From 6dc920de59b03d9510f696616f6058fabf7bfda2 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Mon, 4 Sep 2006 15:07:46 +0000 Subject: [PATCH] sslinfo contrib module - information about current SSL certificate Author: Victor Wagner --- contrib/Makefile | 6 +- contrib/sslinfo/Makefile | 9 + contrib/sslinfo/README.sslinfo | 121 ++++++++++++ contrib/sslinfo/sslinfo.c | 339 +++++++++++++++++++++++++++++++++ contrib/sslinfo/sslinfo.sql.in | 29 +++ src/include/miscadmin.h | 4 +- 6 files changed, 505 insertions(+), 3 deletions(-) create mode 100644 contrib/sslinfo/Makefile create mode 100644 contrib/sslinfo/README.sslinfo create mode 100644 contrib/sslinfo/sslinfo.c create mode 100644 contrib/sslinfo/sslinfo.sql.in diff --git a/contrib/Makefile b/contrib/Makefile index 500ddd2468..475599c5bd 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -1,4 +1,4 @@ -# $PostgreSQL: pgsql/contrib/Makefile,v 1.66 2006/05/30 13:25:57 momjian Exp $ +# $PostgreSQL: pgsql/contrib/Makefile,v 1.67 2006/09/04 15:07:46 petere Exp $ subdir = contrib top_builddir = .. @@ -36,6 +36,10 @@ WANTED_DIRS = \ userlock \ vacuumlo +ifeq ($(with_openssl),yes) +WANTED_DIRS += sslinfo +endif + # Missing: # adddepend \ (does not have a makefile) # mSQL-interface \ (requires msql installed) diff --git a/contrib/sslinfo/Makefile b/contrib/sslinfo/Makefile new file mode 100644 index 0000000000..4e1caf5432 --- /dev/null +++ b/contrib/sslinfo/Makefile @@ -0,0 +1,9 @@ +subdir = contrib/sslinfo +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global + +MODULES = sslinfo +DATA_built = sslinfo.sql +DOC = README.pgsslinfo + +include ../contrib-global.mk diff --git a/contrib/sslinfo/README.sslinfo b/contrib/sslinfo/README.sslinfo new file mode 100644 index 0000000000..f85413d66e --- /dev/null +++ b/contrib/sslinfo/README.sslinfo @@ -0,0 +1,121 @@ +sslinfo - information about current SSL certificate for PostgreSQL +================================================================== +Copyright (c) 2006 Cryptocom LTD +Author: Victor Wagner +E-Mail of Cryptocom OpenSSL development group: + + +1. Notes +-------- +This extension won't build unless your PostgreSQL server is configured +with --with-openssl. Information provided with these functions would +be completely useless if you don't use SSL to connect to database. + + +2. Functions Description +------------------------ + +2.1. ssl_is_used() +~~~~~~~~~~~~~~~~~~ + + ssl_is_used() RETURNS boolean; + +Returns TRUE, if current connection to server uses SSL and FALSE +otherwise. + +2.2. ssl_client_cert_present() +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + ssl_client_cert_present() RETURNS boolean + +Returns TRUE if current client have presented valid SSL client +certificate to the server and FALSE otherwise (e.g., no SSL, +certificate hadn't be requested by server). + +2.3. ssl_client_serial() +~~~~~~~~~~~~~~~~~~~~~~~~ + + ssl_client_serial() RETURNS numeric + +Returns serial number of current client certificate. The combination +of certificate serial number and certificate issuer is guaranteed to +uniquely identify certificate (but not its owner -- the owner ought to +regularily change his keys, and get new certificates from the issuer). + +So, if you run you own CA and allow only certificates from this CA to +be accepted by server, the serial number is the most reliable (albeit +not very mnemonic) means to indentify user. + +2.4. ssl_client_dn() +~~~~~~~~~~~~~~~~~~~~ + + ssl_client_dn() RETURNS text + +Returns the full subject of current client certificate, converting +character data into the current database encoding. It is assumed that +if you use non-Latin characters in the certificate names, your +database is able to represent these characters, too. If your database +uses the SQL_ASCII encoding, non-Latin characters in the name will be +represented as UTF-8 sequences. + +The result looks like '/CN=Somebody /C=Some country/O=Some organization'. + +2.5. ssl_issuer_dn() +~~~~~~~~~~~~~~~~~~~~ + +Returns the full issuer name of the client certificate, converting +character data into current database encoding. + +The combination of the return value of this function with the +certificate serial number uniquely identifies the certificate. + +The result of this function is really useful only if you have more +than one trusted CA certificate in your server's root.crt file, or if +this CA has issued some intermediate certificate authority +certificates. + +2.6. ssl_client_dn_field() +~~~~~~~~~~~~~~~~~~~~~~~~~~ + + ssl_client_dn_field(fieldName text) RETURNS text + +This function returns the value of the specified field in the +certificate subject. Field names are string constants that are +converted into ASN1 object identificators using the OpenSSL object +database. The following values are acceptable: + + commonName (alias CN) + surname (alias SN) + name + givenName (alias GN) + countryName (alias C) + localityName (alias L) + stateOrProvinceName (alias ST) + organizationName (alias O) + organizationUnitName (alias OU) + title + description + initials + postalCode + streetAddress + generationQualifier + description + dnQualifier + x500UniqueIdentifier + pseudonim + role + emailAddress + +All of these fields are optional, except commonName. It depends +entirely on your CA policy which of them would be included and which +wouldn't. The meaning of these fields, howeer, is strictly defined by +the X.500 and X.509 standards, so you cannot just assign arbitrary +meaning to them. + +2.7 ssl_issuer_field() +~~~~~~~~~~~~~~~~~~~ + + ssl_issuer_field(fieldName text) RETURNS text; + +Does same as ssl_client_dn_field, but for the certificate issuer +rather than the certificate subject. diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c new file mode 100644 index 0000000000..2f0569b3f9 --- /dev/null +++ b/contrib/sslinfo/sslinfo.c @@ -0,0 +1,339 @@ +/* + * module for PostgreSQL to access client SSL certificate information + * + * Copyright (c) Cryptocom LTD, 2006 + * Written by Victor B. Wagner + * This file is distributed under BSD-style license. + */ + +#include "postgres.h" +#include "fmgr.h" +#include "utils/numeric.h" +#include "libpq/libpq-be.h" +#include "miscadmin.h" +#include "utils/builtins.h" +#include "mb/pg_wchar.h" + +#include +#include + + +PG_MODULE_MAGIC; + + +Datum ssl_is_used(PG_FUNCTION_ARGS); +Datum ssl_client_cert_present(PG_FUNCTION_ARGS); +Datum ssl_client_serial(PG_FUNCTION_ARGS); +Datum ssl_client_dn_field(PG_FUNCTION_ARGS); +Datum ssl_issuer_field(PG_FUNCTION_ARGS); +Datum ssl_client_dn(PG_FUNCTION_ARGS); +Datum ssl_issuer_dn(PG_FUNCTION_ARGS); +Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName); +Datum X509_NAME_to_text(X509_NAME *name); +Datum ASN1_STRING_to_text(ASN1_STRING *str); + + +/* + * Indicates whether current session uses SSL + * + * Function has no arguments. Returns bool. True if current session + * is SSL session and false if it is local or non-ssl session. + */ +PG_FUNCTION_INFO_V1(ssl_is_used); +Datum ssl_is_used(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(MyProcPort->ssl !=NULL); +} + + +/* + * Indicates whether current client have provided a certificate + * + * Function has no arguments. Returns bool. True if current session + * is SSL session and client certificate is verified, otherwise false. + */ +PG_FUNCTION_INFO_V1(ssl_client_cert_present); +Datum ssl_client_cert_present(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(MyProcPort->peer != NULL); +} + + +/* + * Returns serial number of certificate used to establish current + * session + * + * Function has no arguments. It returns the certificate serial + * number as numeric or null if current session doesn't use SSL or if + * SSL connection is established without sending client certificate. + */ +PG_FUNCTION_INFO_V1(ssl_client_serial); +Datum ssl_client_serial(PG_FUNCTION_ARGS) +{ + Datum result; + Port *port = MyProcPort; + X509 *peer = port->peer; + ASN1_INTEGER *serial = NULL; + BIGNUM *b; + char *decimal; + + if (!peer) + PG_RETURN_NULL(); + serial = X509_get_serialNumber(peer); + b = ASN1_INTEGER_to_BN(serial, NULL); + decimal = BN_bn2dec(b); + BN_free(b); + result = DirectFunctionCall3(numeric_in, + CStringGetDatum(decimal), + ObjectIdGetDatum(0), + Int32GetDatum(-1)); + OPENSSL_free(decimal); + return result; +} + + +/* + * Converts OpenSSL ASN1_STRING structure into text + * + * Converts ASN1_STRING into text, converting all the characters into + * current database encoding if possible. Any invalid characters are + * replaced by question marks. + * + * Parameter: str - OpenSSL ASN1_STRING structure. Memory managment + * of this structure is responsibility of caller. + * + * Returns Datum, which can be directly returned from a C language SQL + * function. + */ +Datum ASN1_STRING_to_text(ASN1_STRING *str) +{ + BIO *membuf = NULL; + size_t size, outlen; + char *sp; + char *dp; + text *result; + + membuf = BIO_new(BIO_s_mem()); + BIO_set_close(membuf, BIO_CLOSE); + ASN1_STRING_print_ex(membuf,str, + ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB) + | ASN1_STRFLGS_UTF8_CONVERT)); + + outlen = 0; + BIO_write(membuf, &outlen, 1); + size = BIO_get_mem_data(membuf, &sp); + dp = pg_do_encoding_conversion(sp, size-1, PG_UTF8, GetDatabaseEncoding()); + outlen = strlen(dp); + result = palloc(VARHDRSZ + outlen); + memcpy(VARDATA(result), dp, outlen); + if (dp != sp) + pfree(dp); + + BIO_free(membuf); + VARATT_SIZEP(result) = outlen + VARHDRSZ; + PG_RETURN_TEXT_P(result); +} + + +/* + * Returns specified field of specified X509_NAME structure + * + * Common part of ssl_client_dn and ssl_issuer_dn functions. + * + * Parameter: X509_NAME *name - either subject or issuer of certificate + * Parameter: text fieldName - field name string like 'CN' or commonName + * to be looked up in the OpenSSL ASN1 OID database + * + * Returns result of ASN1_STRING_to_text applied to appropriate + * part of name + */ +Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName) +{ + char *sp; + char *string_fieldname; + char *dp; + size_t name_len = VARSIZE(fieldName) - VARHDRSZ; + int nid, index, i; + ASN1_STRING *data; + + string_fieldname = palloc(name_len + 1); + sp = VARDATA(fieldName); + dp = string_fieldname; + for (i = 0; i < name_len; i++) + *dp++ = *sp++; + *dp = '\0'; + nid = OBJ_txt2nid(string_fieldname); + if (nid == NID_undef) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid X.509 field name: \"%s\"", + string_fieldname))); + pfree(string_fieldname); + index = X509_NAME_get_index_by_NID(name, nid, -1); + if (index < 0) + return (Datum)0; + data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, index)); + return ASN1_STRING_to_text(data); +} + + +/* + * Returns specified field of client certificate distinguished name + * + * Receives field name (like 'commonName' and 'emailAddress') and + * returns appropriate part of certificate subject converted into + * database encoding. + * + * Parameter: fieldname text - will be looked up in OpenSSL object + * identifier database + * + * Returns text string with appropriate value. + * + * Throws an error if argument cannot be converted into ASN1 OID by + * OpenSSL. Returns null if no client certificate is present, or if + * there is no field with such name in the certificate. + */ +PG_FUNCTION_INFO_V1(ssl_client_dn_field); +Datum ssl_client_dn_field(PG_FUNCTION_ARGS) +{ + text *fieldname = PG_GETARG_TEXT_P(0); + Datum result; + + if (!(MyProcPort->peer)) + PG_RETURN_NULL(); + + result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname); + + if (!result) + PG_RETURN_NULL(); + else + return result; +} + + +/* + * Returns specified field of client certificate issuer name + * + * Receives field name (like 'commonName' and 'emailAddress') and + * returns appropriate part of certificate subject converted into + * database encoding. + * + * Parameter: fieldname text - would be looked up in OpenSSL object + * identifier database + * + * Returns text string with appropriate value. + * + * Throws an error if argument cannot be converted into ASN1 OID by + * OpenSSL. Returns null if no client certificate is present, or if + * there is no field with such name in the certificate. + */ +PG_FUNCTION_INFO_V1(ssl_issuer_field); +Datum ssl_issuer_field(PG_FUNCTION_ARGS) +{ + text *fieldname = PG_GETARG_TEXT_P(0); + Datum result; + + if (!(MyProcPort->peer)) + PG_RETURN_NULL(); + + result = X509_NAME_field_to_text(X509_get_issuer_name(MyProcPort->peer), fieldname); + + if (!result) + PG_RETURN_NULL(); + else + return result; +} + + +/* + * Equivalent of X509_NAME_oneline that respects encoding + * + * This function converts X509_NAME structure to the text variable + * converting all textual data into current database encoding. + * + * Parameter: X509_NAME *name X509_NAME structure to be converted + * + * Returns: text datum which contains string representation of + * X509_NAME + */ +Datum X509_NAME_to_text(X509_NAME *name) +{ + BIO *membuf = BIO_new(BIO_s_mem()); + int i,nid,count = X509_NAME_entry_count(name); + X509_NAME_ENTRY *e; + ASN1_STRING *v; + + const char *field_name; + size_t size,outlen; + char *sp; + char *dp; + text *result; + + BIO_set_close(membuf, BIO_CLOSE); + for (i=0; i