]> granicus.if.org Git - postgresql/blob - contrib/sslinfo/sslinfo.c
Fix old bug in contrib/sslinfo: X509_NAME_to_text freed the BIO_s_mem buffer
[postgresql] / contrib / sslinfo / sslinfo.c
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  * $PostgreSQL: pgsql/contrib/sslinfo/sslinfo.c,v 1.8 2008/11/10 14:57:38 tgl Exp $
8  */
9
10 #include "postgres.h"
11 #include "fmgr.h"
12 #include "utils/numeric.h"
13 #include "libpq/libpq-be.h"
14 #include "miscadmin.h"
15 #include "utils/builtins.h"
16 #include "mb/pg_wchar.h"
17
18 #include <openssl/x509.h>
19 #include <openssl/asn1.h>
20
21
22 PG_MODULE_MAGIC;
23
24
25 Datum           ssl_is_used(PG_FUNCTION_ARGS);
26 Datum           ssl_client_cert_present(PG_FUNCTION_ARGS);
27 Datum           ssl_client_serial(PG_FUNCTION_ARGS);
28 Datum           ssl_client_dn_field(PG_FUNCTION_ARGS);
29 Datum           ssl_issuer_field(PG_FUNCTION_ARGS);
30 Datum           ssl_client_dn(PG_FUNCTION_ARGS);
31 Datum           ssl_issuer_dn(PG_FUNCTION_ARGS);
32 Datum           X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
33 Datum           X509_NAME_to_text(X509_NAME *name);
34 Datum           ASN1_STRING_to_text(ASN1_STRING *str);
35
36
37 /*
38  * Indicates whether current session uses SSL
39  *
40  * Function has no arguments.  Returns bool.  True if current session
41  * is SSL session and false if it is local or non-ssl session.
42  */
43 PG_FUNCTION_INFO_V1(ssl_is_used);
44 Datum
45 ssl_is_used(PG_FUNCTION_ARGS)
46 {
47         PG_RETURN_BOOL(MyProcPort->ssl != NULL);
48 }
49
50
51 /*
52  * Indicates whether current client have provided a certificate
53  *
54  * Function has no arguments.  Returns bool.  True if current session
55  * is SSL session and client certificate is verified, otherwise false.
56  */
57 PG_FUNCTION_INFO_V1(ssl_client_cert_present);
58 Datum
59 ssl_client_cert_present(PG_FUNCTION_ARGS)
60 {
61         PG_RETURN_BOOL(MyProcPort->peer != NULL);
62 }
63
64
65 /*
66  * Returns serial number of certificate used to establish current
67  * session
68  *
69  * Function has no arguments.  It returns the certificate serial
70  * number as numeric or null if current session doesn't use SSL or if
71  * SSL connection is established without sending client certificate.
72  */
73 PG_FUNCTION_INFO_V1(ssl_client_serial);
74 Datum
75 ssl_client_serial(PG_FUNCTION_ARGS)
76 {
77         Datum           result;
78         Port       *port = MyProcPort;
79         X509       *peer = port->peer;
80         ASN1_INTEGER *serial = NULL;
81         BIGNUM     *b;
82         char       *decimal;
83
84         if (!peer)
85                 PG_RETURN_NULL();
86         serial = X509_get_serialNumber(peer);
87         b = ASN1_INTEGER_to_BN(serial, NULL);
88         decimal = BN_bn2dec(b);
89
90         BN_free(b);
91         result = DirectFunctionCall3(numeric_in,
92                                                                  CStringGetDatum(decimal),
93                                                                  ObjectIdGetDatum(0),
94                                                                  Int32GetDatum(-1));
95         OPENSSL_free(decimal);
96         return result;
97 }
98
99
100 /*
101  * Converts OpenSSL ASN1_STRING structure into text
102  *
103  * Converts ASN1_STRING into text, converting all the characters into
104  * current database encoding if possible.  Any invalid characters are
105  * replaced by question marks.
106  *
107  * Parameter: str - OpenSSL ASN1_STRING structure.      Memory managment
108  * of this structure is responsibility of caller.
109  *
110  * Returns Datum, which can be directly returned from a C language SQL
111  * function.
112  */
113 Datum
114 ASN1_STRING_to_text(ASN1_STRING *str)
115 {
116         BIO                *membuf;
117         size_t          size;
118         char            nullterm;
119         char       *sp;
120         char       *dp;
121         text       *result;
122
123         membuf = BIO_new(BIO_s_mem());
124         (void) BIO_set_close(membuf, BIO_CLOSE);
125         ASN1_STRING_print_ex(membuf, str,
126                                                  ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
127                                                   | ASN1_STRFLGS_UTF8_CONVERT));
128         /* ensure null termination of the BIO's content */
129         nullterm = '\0';
130         BIO_write(membuf, &nullterm, 1);
131         size = BIO_get_mem_data(membuf, &sp);
132         dp = (char *) pg_do_encoding_conversion((unsigned char *) sp,
133                                                                                         size - 1,
134                                                                                         PG_UTF8,
135                                                                                         GetDatabaseEncoding());
136         result = cstring_to_text(dp);
137         if (dp != sp)
138                 pfree(dp);
139         BIO_free(membuf);
140
141         PG_RETURN_TEXT_P(result);
142 }
143
144
145 /*
146  * Returns specified field of specified X509_NAME structure
147  *
148  * Common part of ssl_client_dn and ssl_issuer_dn functions.
149  *
150  * Parameter: X509_NAME *name - either subject or issuer of certificate
151  * Parameter: text fieldName  - field name string like 'CN' or commonName
152  *                        to be looked up in the OpenSSL ASN1 OID database
153  *
154  * Returns result of ASN1_STRING_to_text applied to appropriate
155  * part of name
156  */
157 Datum
158 X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
159 {
160         char       *string_fieldname;
161         int                     nid,
162                                 index;
163         ASN1_STRING *data;
164
165         string_fieldname = text_to_cstring(fieldName);
166         nid = OBJ_txt2nid(string_fieldname);
167         if (nid == NID_undef)
168                 ereport(ERROR,
169                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
170                                  errmsg("invalid X.509 field name: \"%s\"",
171                                                 string_fieldname)));
172         pfree(string_fieldname);
173         index = X509_NAME_get_index_by_NID(name, nid, -1);
174         if (index < 0)
175                 return (Datum) 0;
176         data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, index));
177         return ASN1_STRING_to_text(data);
178 }
179
180
181 /*
182  * Returns specified field of client certificate distinguished name
183  *
184  * Receives field name (like 'commonName' and 'emailAddress') and
185  * returns appropriate part of certificate subject converted into
186  * database encoding.
187  *
188  * Parameter: fieldname text - will be looked up in OpenSSL object
189  * identifier database
190  *
191  * Returns text string with appropriate value.
192  *
193  * Throws an error if argument cannot be converted into ASN1 OID by
194  * OpenSSL.  Returns null if no client certificate is present, or if
195  * there is no field with such name in the certificate.
196  */
197 PG_FUNCTION_INFO_V1(ssl_client_dn_field);
198 Datum
199 ssl_client_dn_field(PG_FUNCTION_ARGS)
200 {
201         text       *fieldname = PG_GETARG_TEXT_P(0);
202         Datum           result;
203
204         if (!(MyProcPort->peer))
205                 PG_RETURN_NULL();
206
207         result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname);
208
209         if (!result)
210                 PG_RETURN_NULL();
211         else
212                 return result;
213 }
214
215
216 /*
217  * Returns specified field of client certificate issuer name
218  *
219  * Receives field name (like 'commonName' and 'emailAddress') and
220  * returns appropriate part of certificate subject converted into
221  * database encoding.
222  *
223  * Parameter: fieldname text - would be looked up in OpenSSL object
224  * identifier database
225  *
226  * Returns text string with appropriate value.
227  *
228  * Throws an error if argument cannot be converted into ASN1 OID by
229  * OpenSSL.  Returns null if no client certificate is present, or if
230  * there is no field with such name in the certificate.
231  */
232 PG_FUNCTION_INFO_V1(ssl_issuer_field);
233 Datum
234 ssl_issuer_field(PG_FUNCTION_ARGS)
235 {
236         text       *fieldname = PG_GETARG_TEXT_P(0);
237         Datum           result;
238
239         if (!(MyProcPort->peer))
240                 PG_RETURN_NULL();
241
242         result = X509_NAME_field_to_text(X509_get_issuer_name(MyProcPort->peer), fieldname);
243
244         if (!result)
245                 PG_RETURN_NULL();
246         else
247                 return result;
248 }
249
250
251 /*
252  * Equivalent of X509_NAME_oneline that respects encoding
253  *
254  * This function converts X509_NAME structure to the text variable
255  * converting all textual data into current database encoding.
256  *
257  * Parameter: X509_NAME *name X509_NAME structure to be converted
258  *
259  * Returns: text datum which contains string representation of
260  * X509_NAME
261  */
262 Datum
263 X509_NAME_to_text(X509_NAME *name)
264 {
265         BIO                *membuf = BIO_new(BIO_s_mem());
266         int                     i,
267                                 nid,
268                                 count = X509_NAME_entry_count(name);
269         X509_NAME_ENTRY *e;
270         ASN1_STRING *v;
271         const char *field_name;
272         size_t          size;
273         char            nullterm;
274         char       *sp;
275         char       *dp;
276         text       *result;
277
278         (void) BIO_set_close(membuf, BIO_CLOSE);
279         for (i = 0; i < count; i++)
280         {
281                 e = X509_NAME_get_entry(name, i);
282                 nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
283                 v = X509_NAME_ENTRY_get_data(e);
284                 field_name = OBJ_nid2sn(nid);
285                 if (!field_name)
286                         field_name = OBJ_nid2ln(nid);
287                 BIO_printf(membuf, "/%s=", field_name);
288                 ASN1_STRING_print_ex(membuf, v,
289                                                          ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
290                                                           | ASN1_STRFLGS_UTF8_CONVERT));
291         }
292
293         /* ensure null termination of the BIO's content */
294         nullterm = '\0';
295         BIO_write(membuf, &nullterm, 1);
296         size = BIO_get_mem_data(membuf, &sp);
297         dp = (char *) pg_do_encoding_conversion((unsigned char *) sp,
298                                                                                         size - 1,
299                                                                                         PG_UTF8,
300                                                                                         GetDatabaseEncoding());
301         result = cstring_to_text(dp);
302         if (dp != sp)
303                 pfree(dp);
304         BIO_free(membuf);
305
306         PG_RETURN_TEXT_P(result);
307 }
308
309
310 /*
311  * Returns current client certificate subject as one string
312  *
313  * This function returns distinguished name (subject) of the client
314  * certificate used in the current SSL connection, converting it into
315  * the current database encoding.
316  *
317  * Returns text datum.
318  */
319 PG_FUNCTION_INFO_V1(ssl_client_dn);
320 Datum
321 ssl_client_dn(PG_FUNCTION_ARGS)
322 {
323         if (!(MyProcPort->peer))
324                 PG_RETURN_NULL();
325         return X509_NAME_to_text(X509_get_subject_name(MyProcPort->peer));
326 }
327
328
329 /*
330  * Returns current client certificate issuer as one string
331  *
332  * This function returns issuer's distinguished name of the client
333  * certificate used in the current SSL connection, converting it into
334  * the current database encoding.
335  *
336  * Returns text datum.
337  */
338 PG_FUNCTION_INFO_V1(ssl_issuer_dn);
339 Datum
340 ssl_issuer_dn(PG_FUNCTION_ARGS)
341 {
342         if (!(MyProcPort->peer))
343                 PG_RETURN_NULL();
344         return X509_NAME_to_text(X509_get_issuer_name(MyProcPort->peer));
345 }