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