2 * module for PostgreSQL to access client SSL certificate information
4 * Written by Victor B. Wagner <vitus@cryptocom.ru>, Cryptocom LTD
5 * This file is distributed under BSD-style license.
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"
16 #include <openssl/x509.h>
17 #include <openssl/asn1.h>
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);
36 * Indicates whether current session uses SSL
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.
41 PG_FUNCTION_INFO_V1(ssl_is_used);
42 Datum ssl_is_used(PG_FUNCTION_ARGS)
44 PG_RETURN_BOOL(MyProcPort->ssl !=NULL);
49 * Indicates whether current client have provided a certificate
51 * Function has no arguments. Returns bool. True if current session
52 * is SSL session and client certificate is verified, otherwise false.
54 PG_FUNCTION_INFO_V1(ssl_client_cert_present);
55 Datum ssl_client_cert_present(PG_FUNCTION_ARGS)
57 PG_RETURN_BOOL(MyProcPort->peer != NULL);
62 * Returns serial number of certificate used to establish current
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.
69 PG_FUNCTION_INFO_V1(ssl_client_serial);
70 Datum ssl_client_serial(PG_FUNCTION_ARGS)
73 Port *port = MyProcPort;
74 X509 *peer = port->peer;
75 ASN1_INTEGER *serial = NULL;
81 serial = X509_get_serialNumber(peer);
82 b = ASN1_INTEGER_to_BN(serial, NULL);
83 decimal = BN_bn2dec(b);
85 result = DirectFunctionCall3(numeric_in,
86 CStringGetDatum(decimal),
89 OPENSSL_free(decimal);
95 * Converts OpenSSL ASN1_STRING structure into text
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.
101 * Parameter: str - OpenSSL ASN1_STRING structure. Memory managment
102 * of this structure is responsibility of caller.
104 * Returns Datum, which can be directly returned from a C language SQL
107 Datum ASN1_STRING_to_text(ASN1_STRING *str)
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));
122 BIO_write(membuf, &outlen, 1);
123 size = BIO_get_mem_data(membuf, &sp);
124 dp = (char *) pg_do_encoding_conversion((unsigned char *) sp,
127 GetDatabaseEncoding());
129 result = palloc(VARHDRSZ + outlen);
130 memcpy(VARDATA(result), dp, outlen);
135 VARATT_SIZEP(result) = outlen + VARHDRSZ;
136 PG_RETURN_TEXT_P(result);
141 * Returns specified field of specified X509_NAME structure
143 * Common part of ssl_client_dn and ssl_issuer_dn functions.
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
149 * Returns result of ASN1_STRING_to_text applied to appropriate
152 Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
155 char *string_fieldname;
157 size_t name_len = VARSIZE(fieldName) - VARHDRSZ;
161 string_fieldname = palloc(name_len + 1);
162 sp = VARDATA(fieldName);
163 dp = string_fieldname;
164 for (i = 0; i < name_len; i++)
167 nid = OBJ_txt2nid(string_fieldname);
168 if (nid == NID_undef)
170 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
171 errmsg("invalid X.509 field name: \"%s\"",
173 pfree(string_fieldname);
174 index = X509_NAME_get_index_by_NID(name, nid, -1);
177 data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, index));
178 return ASN1_STRING_to_text(data);
183 * Returns specified field of client certificate distinguished name
185 * Receives field name (like 'commonName' and 'emailAddress') and
186 * returns appropriate part of certificate subject converted into
189 * Parameter: fieldname text - will be looked up in OpenSSL object
190 * identifier database
192 * Returns text string with appropriate value.
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.
198 PG_FUNCTION_INFO_V1(ssl_client_dn_field);
199 Datum ssl_client_dn_field(PG_FUNCTION_ARGS)
201 text *fieldname = PG_GETARG_TEXT_P(0);
204 if (!(MyProcPort->peer))
207 result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname);
217 * Returns specified field of client certificate issuer name
219 * Receives field name (like 'commonName' and 'emailAddress') and
220 * returns appropriate part of certificate subject converted into
223 * Parameter: fieldname text - would be looked up in OpenSSL object
224 * identifier database
226 * Returns text string with appropriate value.
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.
232 PG_FUNCTION_INFO_V1(ssl_issuer_field);
233 Datum ssl_issuer_field(PG_FUNCTION_ARGS)
235 text *fieldname = PG_GETARG_TEXT_P(0);
238 if (!(MyProcPort->peer))
241 result = X509_NAME_field_to_text(X509_get_issuer_name(MyProcPort->peer), fieldname);
251 * Equivalent of X509_NAME_oneline that respects encoding
253 * This function converts X509_NAME structure to the text variable
254 * converting all textual data into current database encoding.
256 * Parameter: X509_NAME *name X509_NAME structure to be converted
258 * Returns: text datum which contains string representation of
261 Datum X509_NAME_to_text(X509_NAME *name)
263 BIO *membuf = BIO_new(BIO_s_mem());
264 int i,nid,count = X509_NAME_entry_count(name);
268 const char *field_name;
274 BIO_set_close(membuf, BIO_CLOSE);
275 for (i=0; i<count; i++)
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);
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));
290 BIO_write(membuf, &i, 1);
291 size = BIO_get_mem_data(membuf, &sp);
293 dp = (char *) pg_do_encoding_conversion((unsigned char *) sp,
296 GetDatabaseEncoding());
299 result = palloc(VARHDRSZ + outlen);
300 memcpy(VARDATA(result), dp, outlen);
302 /* pg_do_encoding_conversion has annoying habit of returning
306 VARATT_SIZEP(result) = outlen + VARHDRSZ;
307 PG_RETURN_TEXT_P(result);
312 * Returns current client certificate subject as one string
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.
318 * Returns text datum.
320 PG_FUNCTION_INFO_V1(ssl_client_dn);
321 Datum ssl_client_dn(PG_FUNCTION_ARGS)
323 if (!(MyProcPort->peer))
325 return X509_NAME_to_text(X509_get_subject_name(MyProcPort->peer));
330 * Returns current client certificate issuer as one string
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.
336 * Returns text datum.
338 PG_FUNCTION_INFO_V1(ssl_issuer_dn);
339 Datum ssl_issuer_dn(PG_FUNCTION_ARGS)
341 if (!(MyProcPort->peer))
343 return X509_NAME_to_text(X509_get_issuer_name(MyProcPort->peer));