mirror of
				https://github.com/postgres/postgres.git
				synced 2025-10-29 22:49:41 +03:00 
			
		
		
		
	Re-ordering the #include's is a bit problematic here because libpq/libpq-be.h needs to include <openssl/ssl.h>. Instead, let's #undef the unwanted macro after all the #includes. This is definitely uglier than the other way, but it should work despite possible future header rearrangements. (A look at the openssl headers indicates that X509_NAME is the only conflicting symbol that we use.) In passing, remove a related but long-incorrect comment in pg_backup_archiver.h. Discussion: https://postgr.es/m/1051867.1635720347@sss.pgh.pa.us
		
			
				
	
	
		
			485 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			485 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * module for PostgreSQL to access client SSL certificate information
 | |
|  *
 | |
|  * Written by Victor B. Wagner <vitus@cryptocom.ru>, Cryptocom LTD
 | |
|  * This file is distributed under BSD-style license.
 | |
|  *
 | |
|  * contrib/sslinfo/sslinfo.c
 | |
|  */
 | |
| 
 | |
| #include "postgres.h"
 | |
| 
 | |
| #include <openssl/x509.h>
 | |
| #include <openssl/x509v3.h>
 | |
| #include <openssl/asn1.h>
 | |
| 
 | |
| #include "access/htup_details.h"
 | |
| #include "funcapi.h"
 | |
| #include "libpq/libpq-be.h"
 | |
| #include "miscadmin.h"
 | |
| #include "utils/builtins.h"
 | |
| 
 | |
| /*
 | |
|  * On Windows, <wincrypt.h> includes a #define for X509_NAME, which breaks our
 | |
|  * ability to use OpenSSL's version of that symbol if <wincrypt.h> is pulled
 | |
|  * in after <openssl/ssl.h> ... and, at least on some builds, it is.  We
 | |
|  * can't reliably fix that by re-ordering #includes, because libpq/libpq-be.h
 | |
|  * #includes <openssl/ssl.h>.  Instead, just zap the #define again here.
 | |
|  */
 | |
| #ifdef X509_NAME
 | |
| #undef X509_NAME
 | |
| #endif
 | |
| 
 | |
| PG_MODULE_MAGIC;
 | |
| 
 | |
| static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
 | |
| static Datum ASN1_STRING_to_text(ASN1_STRING *str);
 | |
| 
 | |
| /*
 | |
|  * Function context for data persisting over repeated calls.
 | |
|  */
 | |
| typedef struct
 | |
| {
 | |
| 	TupleDesc	tupdesc;
 | |
| } SSLExtensionInfoContext;
 | |
| 
 | |
| /*
 | |
|  * 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_in_use);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Returns SSL version currently in use.
 | |
|  */
 | |
| PG_FUNCTION_INFO_V1(ssl_version);
 | |
| Datum
 | |
| ssl_version(PG_FUNCTION_ARGS)
 | |
| {
 | |
| 	const char *version;
 | |
| 
 | |
| 	if (!MyProcPort->ssl_in_use)
 | |
| 		PG_RETURN_NULL();
 | |
| 
 | |
| 	version = be_tls_get_version(MyProcPort);
 | |
| 	if (version == NULL)
 | |
| 		PG_RETURN_NULL();
 | |
| 
 | |
| 	PG_RETURN_TEXT_P(cstring_to_text(version));
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Returns SSL cipher currently in use.
 | |
|  */
 | |
| PG_FUNCTION_INFO_V1(ssl_cipher);
 | |
| Datum
 | |
| ssl_cipher(PG_FUNCTION_ARGS)
 | |
| {
 | |
| 	const char *cipher;
 | |
| 
 | |
| 	if (!MyProcPort->ssl_in_use)
 | |
| 		PG_RETURN_NULL();
 | |
| 
 | |
| 	cipher = be_tls_get_cipher(MyProcPort);
 | |
| 	if (cipher == NULL)
 | |
| 		PG_RETURN_NULL();
 | |
| 
 | |
| 	PG_RETURN_TEXT_P(cstring_to_text(cipher));
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Indicates whether current client 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_cert_valid);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * 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)
 | |
| {
 | |
| 	char decimal[NAMEDATALEN];
 | |
| 	Datum		result;
 | |
| 
 | |
| 	if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
 | |
| 		PG_RETURN_NULL();
 | |
| 
 | |
| 	be_tls_get_peer_serial(MyProcPort, decimal, NAMEDATALEN);
 | |
| 
 | |
| 	if (!*decimal)
 | |
| 		PG_RETURN_NULL();
 | |
| 
 | |
| 	result = DirectFunctionCall3(numeric_in,
 | |
| 								 CStringGetDatum(decimal),
 | |
| 								 ObjectIdGetDatum(0),
 | |
| 								 Int32GetDatum(-1));
 | |
| 	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 management
 | |
|  * of this structure is responsibility of caller.
 | |
|  *
 | |
|  * Returns Datum, which can be directly returned from a C language SQL
 | |
|  * function.
 | |
|  */
 | |
| static Datum
 | |
| ASN1_STRING_to_text(ASN1_STRING *str)
 | |
| {
 | |
| 	BIO		   *membuf;
 | |
| 	size_t		size;
 | |
| 	char		nullterm;
 | |
| 	char	   *sp;
 | |
| 	char	   *dp;
 | |
| 	text	   *result;
 | |
| 
 | |
| 	membuf = BIO_new(BIO_s_mem());
 | |
| 	if (membuf == NULL)
 | |
| 		ereport(ERROR,
 | |
| 				(errcode(ERRCODE_OUT_OF_MEMORY),
 | |
| 				 errmsg("could not create OpenSSL BIO structure")));
 | |
| 	(void) BIO_set_close(membuf, BIO_CLOSE);
 | |
| 	ASN1_STRING_print_ex(membuf, str,
 | |
| 						 ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
 | |
| 						  | ASN1_STRFLGS_UTF8_CONVERT));
 | |
| 	/* ensure null termination of the BIO's content */
 | |
| 	nullterm = '\0';
 | |
| 	BIO_write(membuf, &nullterm, 1);
 | |
| 	size = BIO_get_mem_data(membuf, &sp);
 | |
| 	dp = pg_any_to_server(sp, size - 1, PG_UTF8);
 | |
| 	result = cstring_to_text(dp);
 | |
| 	if (dp != sp)
 | |
| 		pfree(dp);
 | |
| 	if (BIO_free(membuf) != 1)
 | |
| 		elog(ERROR, "could not free OpenSSL BIO structure");
 | |
| 
 | |
| 	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
 | |
|  */
 | |
| static Datum
 | |
| X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
 | |
| {
 | |
| 	char	   *string_fieldname;
 | |
| 	int			nid,
 | |
| 				index;
 | |
| 	ASN1_STRING *data;
 | |
| 
 | |
| 	string_fieldname = text_to_cstring(fieldName);
 | |
| 	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_PP(0);
 | |
| 	Datum		result;
 | |
| 
 | |
| 	if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
 | |
| 		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_PP(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;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * 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)
 | |
| {
 | |
| 	char		subject[NAMEDATALEN];
 | |
| 
 | |
| 	if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
 | |
| 		PG_RETURN_NULL();
 | |
| 
 | |
| 	be_tls_get_peer_subject_name(MyProcPort, subject, NAMEDATALEN);
 | |
| 
 | |
| 	if (!*subject)
 | |
| 		PG_RETURN_NULL();
 | |
| 
 | |
| 	PG_RETURN_TEXT_P(cstring_to_text(subject));
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * 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)
 | |
| {
 | |
| 	char		issuer[NAMEDATALEN];
 | |
| 
 | |
| 	if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
 | |
| 		PG_RETURN_NULL();
 | |
| 
 | |
| 	be_tls_get_peer_issuer_name(MyProcPort, issuer, NAMEDATALEN);
 | |
| 
 | |
| 	if (!*issuer)
 | |
| 		PG_RETURN_NULL();
 | |
| 
 | |
| 	PG_RETURN_TEXT_P(cstring_to_text(issuer));
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Returns information about available SSL extensions.
 | |
|  *
 | |
|  * Returns setof record made of the following values:
 | |
|  * - name of the extension.
 | |
|  * - value of the extension.
 | |
|  * - critical status of the extension.
 | |
|  */
 | |
| PG_FUNCTION_INFO_V1(ssl_extension_info);
 | |
| Datum
 | |
| ssl_extension_info(PG_FUNCTION_ARGS)
 | |
| {
 | |
| 	X509	   *cert = MyProcPort->peer;
 | |
| 	FuncCallContext *funcctx;
 | |
| 	int			call_cntr;
 | |
| 	int			max_calls;
 | |
| 	MemoryContext oldcontext;
 | |
| 	SSLExtensionInfoContext *fctx;
 | |
| 
 | |
| 	if (SRF_IS_FIRSTCALL())
 | |
| 	{
 | |
| 
 | |
| 		TupleDesc	tupdesc;
 | |
| 
 | |
| 		/* create a function context for cross-call persistence */
 | |
| 		funcctx = SRF_FIRSTCALL_INIT();
 | |
| 
 | |
| 		/*
 | |
| 		 * Switch to memory context appropriate for multiple function calls
 | |
| 		 */
 | |
| 		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
 | |
| 
 | |
| 		/* Create a user function context for cross-call persistence */
 | |
| 		fctx = (SSLExtensionInfoContext *) palloc(sizeof(SSLExtensionInfoContext));
 | |
| 
 | |
| 		/* Construct tuple descriptor */
 | |
| 		if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
 | |
| 			ereport(ERROR,
 | |
| 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 | |
| 					 errmsg("function returning record called in context that cannot accept type record")));
 | |
| 		fctx->tupdesc = BlessTupleDesc(tupdesc);
 | |
| 
 | |
| 		/* Set max_calls as a count of extensions in certificate */
 | |
| 		max_calls = cert != NULL ? X509_get_ext_count(cert) : 0;
 | |
| 
 | |
| 		if (max_calls > 0)
 | |
| 		{
 | |
| 			/* got results, keep track of them */
 | |
| 			funcctx->max_calls = max_calls;
 | |
| 			funcctx->user_fctx = fctx;
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			/* fast track when no results */
 | |
| 			MemoryContextSwitchTo(oldcontext);
 | |
| 			SRF_RETURN_DONE(funcctx);
 | |
| 		}
 | |
| 
 | |
| 		MemoryContextSwitchTo(oldcontext);
 | |
| 	}
 | |
| 
 | |
| 	/* stuff done on every call of the function */
 | |
| 	funcctx = SRF_PERCALL_SETUP();
 | |
| 
 | |
| 	/*
 | |
| 	 * Initialize per-call variables.
 | |
| 	 */
 | |
| 	call_cntr = funcctx->call_cntr;
 | |
| 	max_calls = funcctx->max_calls;
 | |
| 	fctx = funcctx->user_fctx;
 | |
| 
 | |
| 	/* do while there are more left to send */
 | |
| 	if (call_cntr < max_calls)
 | |
| 	{
 | |
| 		Datum		values[3];
 | |
| 		bool		nulls[3];
 | |
| 		char	   *buf;
 | |
| 		HeapTuple	tuple;
 | |
| 		Datum		result;
 | |
| 		BIO		   *membuf;
 | |
| 		X509_EXTENSION *ext;
 | |
| 		ASN1_OBJECT *obj;
 | |
| 		int			nid;
 | |
| 		int			len;
 | |
| 
 | |
| 		/* need a BIO for this */
 | |
| 		membuf = BIO_new(BIO_s_mem());
 | |
| 		if (membuf == NULL)
 | |
| 			ereport(ERROR,
 | |
| 					(errcode(ERRCODE_OUT_OF_MEMORY),
 | |
| 					 errmsg("could not create OpenSSL BIO structure")));
 | |
| 
 | |
| 		/* Get the extension from the certificate */
 | |
| 		ext = X509_get_ext(cert, call_cntr);
 | |
| 		obj = X509_EXTENSION_get_object(ext);
 | |
| 
 | |
| 		/* Get the extension name */
 | |
| 		nid = OBJ_obj2nid(obj);
 | |
| 		if (nid == NID_undef)
 | |
| 			ereport(ERROR,
 | |
| 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 | |
| 					 errmsg("unknown OpenSSL extension in certificate at position %d",
 | |
| 							call_cntr)));
 | |
| 		values[0] = CStringGetTextDatum(OBJ_nid2sn(nid));
 | |
| 		nulls[0] = false;
 | |
| 
 | |
| 		/* Get the extension value */
 | |
| 		if (X509V3_EXT_print(membuf, ext, 0, 0) <= 0)
 | |
| 			ereport(ERROR,
 | |
| 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 | |
| 					 errmsg("could not print extension value in certificate at position %d",
 | |
| 							call_cntr)));
 | |
| 		len = BIO_get_mem_data(membuf, &buf);
 | |
| 		values[1] = PointerGetDatum(cstring_to_text_with_len(buf, len));
 | |
| 		nulls[1] = false;
 | |
| 
 | |
| 		/* Get critical status */
 | |
| 		values[2] = BoolGetDatum(X509_EXTENSION_get_critical(ext));
 | |
| 		nulls[2] = false;
 | |
| 
 | |
| 		/* Build tuple */
 | |
| 		tuple = heap_form_tuple(fctx->tupdesc, values, nulls);
 | |
| 		result = HeapTupleGetDatum(tuple);
 | |
| 
 | |
| 		if (BIO_free(membuf) != 1)
 | |
| 			elog(ERROR, "could not free OpenSSL BIO structure");
 | |
| 
 | |
| 		SRF_RETURN_NEXT(funcctx, result);
 | |
| 	}
 | |
| 
 | |
| 	/* All done */
 | |
| 	SRF_RETURN_DONE(funcctx);
 | |
| }
 |