]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
sslinfo contrib module - information about current SSL certificate
authorPeter Eisentraut <peter_e@gmx.net>
Mon, 4 Sep 2006 15:07:46 +0000 (15:07 +0000)
committerPeter Eisentraut <peter_e@gmx.net>
Mon, 4 Sep 2006 15:07:46 +0000 (15:07 +0000)
Author: Victor Wagner <vitus@cryptocom.ru>

contrib/Makefile
contrib/sslinfo/Makefile [new file with mode: 0644]
contrib/sslinfo/README.sslinfo [new file with mode: 0644]
contrib/sslinfo/sslinfo.c [new file with mode: 0644]
contrib/sslinfo/sslinfo.sql.in [new file with mode: 0644]
src/include/miscadmin.h

index 500ddd246814818ff3fe7e9fc99ed015a996ffe5..475599c5bd06203a6bfc7d037355db04e0c27136 100644 (file)
@@ -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 (file)
index 0000000..4e1caf5
--- /dev/null
@@ -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 (file)
index 0000000..f85413d
--- /dev/null
@@ -0,0 +1,121 @@
+sslinfo - information about current SSL certificate for PostgreSQL
+==================================================================
+Copyright (c) 2006 Cryptocom LTD
+Author: Victor Wagner <vitus@cryptocom.ru>
+E-Mail of Cryptocom OpenSSL development group: <openssl@cryptocom.ru>
+
+
+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 (file)
index 0000000..2f0569b
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ * module for PostgreSQL to access client SSL certificate information
+ *
+ * Copyright (c) Cryptocom LTD, 2006
+ * Written by Victor B. Wagner <vitus@cryptocom.ru>
+ * 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 <openssl/x509.h>
+#include <openssl/asn1.h>
+
+
+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<count; i++)
+       {
+               e = X509_NAME_get_entry(name, i);
+               nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
+               v = X509_NAME_ENTRY_get_data(e);
+               field_name = OBJ_nid2sn(nid);
+               if (!field_name)
+                       field_name = OBJ_nid2ln(nid);
+               BIO_printf(membuf, "/%s=", field_name);
+               ASN1_STRING_print_ex(membuf,v,
+                                                        ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
+                                                         | ASN1_STRFLGS_UTF8_CONVERT));
+       }
+
+       i=0;
+       BIO_write(membuf, &i, 1);
+       size = BIO_get_mem_data(membuf, &sp);
+
+       dp = pg_do_encoding_conversion(sp, size-1, PG_UTF8, GetDatabaseEncoding());
+       BIO_free(membuf);
+       outlen = strlen(dp);
+       result = palloc(VARHDRSZ + outlen);
+       memcpy(VARDATA(result), dp, outlen);
+
+       /* pg_do_encoding_conversion has annoying habit of returning
+        * source pointer */
+       if (dp != sp)
+               pfree(dp);
+       VARATT_SIZEP(result) = outlen + VARHDRSZ;
+       PG_RETURN_TEXT_P(result);
+}
+
+
+/*
+ * Returns current client certificate subject as one string
+ *
+ * This function returns distinguished name (subject) of the client
+ * certificate used in the current SSL connection, converting it into
+ * the current database encoding.
+ *
+ * Returns text datum.
+ */
+PG_FUNCTION_INFO_V1(ssl_client_dn);
+Datum ssl_client_dn(PG_FUNCTION_ARGS)
+{
+       if (!(MyProcPort->peer))
+               PG_RETURN_NULL();
+       return X509_NAME_to_text(X509_get_subject_name(MyProcPort->peer));
+}
+
+
+/*
+ * Returns current client certificate issuer as one string
+ *
+ * This function returns issuer's distinguished name of the client
+ * certificate used in the current SSL connection, converting it into
+ * the current database encoding.
+ *
+ * Returns text datum.
+ */
+PG_FUNCTION_INFO_V1(ssl_issuer_dn);
+Datum ssl_issuer_dn(PG_FUNCTION_ARGS)
+{
+       if (!(MyProcPort->peer))
+               PG_RETURN_NULL();
+       return X509_NAME_to_text(X509_get_issuer_name(MyProcPort->peer));
+}
diff --git a/contrib/sslinfo/sslinfo.sql.in b/contrib/sslinfo/sslinfo.sql.in
new file mode 100644 (file)
index 0000000..5629f16
--- /dev/null
@@ -0,0 +1,29 @@
+SET search_path = public;
+
+CREATE OR REPLACE FUNCTION ssl_client_serial() RETURNS numeric
+    LANGUAGE C STRICT
+    AS 'MODULE_PATHNAME', 'ssl_client_serial';
+
+CREATE OR REPLACE FUNCTION ssl_is_used() RETURNS boolean
+    LANGUAGE C STRICT
+    AS 'MODULE_PATHNAME', 'ssl_is_used';
+
+CREATE OR REPLACE FUNCTION ssl_client_cert_present() RETURNS boolean
+    LANGUAGE C STRICT
+    AS 'MODULE_PATHNAME', 'ssl_client_cert_present';
+
+CREATE OR REPLACE FUNCTION ssl_client_dn_field(text) RETURNS text
+    LANGUAGE C STRICT
+    AS 'MODULE_PATHNAME', 'ssl_client_dn_field';
+
+CREATE OR REPLACE FUNCTION ssl_issuer_field(text) RETURNS text
+    LANGUAGE C STRICT
+    AS 'MODULE_PATHNAME', 'ssl_issuer_field';
+
+CREATE OR REPLACE FUNCTION ssl_client_dn() RETURNS text
+    LANGUAGE C STRICT
+    AS 'MODULE_PATHNAME', 'ssl_client_dn';
+
+CREATE OR REPLACE FUNCTION ssl_issuer_dn() RETURNS text
+    LANGUAGE C STRICT
+    AS 'MODULE_PATHNAME', 'ssl_issuer_dn';
index 8a050f5b7dc5a239642be27b76de4a355a339e2c..887fe8fdb6553bb914754214349d2f1cc1f02c47 100644 (file)
@@ -13,7 +13,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/miscadmin.h,v 1.188 2006/08/15 18:26:59 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/miscadmin.h,v 1.189 2006/09/04 15:07:46 petere Exp $
  *
  * NOTES
  *       some of the information in this file should be moved to other files.
@@ -131,7 +131,7 @@ extern DLLIMPORT int NBuffers;
 extern int     MaxBackends;
 
 extern DLLIMPORT int MyProcPid;
-extern struct Port *MyProcPort;
+extern DLLIMPORT struct Port *MyProcPort;
 extern long MyCancelKey;
 
 extern char OutputFileName[];