openGauss-server/contrib/sslinfo/sslinfo.cpp

331 lines
9.8 KiB
C++

/*
* module for openGauss 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 "knl/knl_variable.h"
#include "fmgr.h"
#include "utils/numeric.h"
#include "utils/numeric_gs.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_version(PG_FUNCTION_ARGS);
Datum ssl_cipher(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(u_sess->proc_cxt.MyProcPort->ssl != NULL);
}
/*
* Returns SSL cipher currently in use.
*/
PG_FUNCTION_INFO_V1(ssl_version);
Datum ssl_version(PG_FUNCTION_ARGS)
{
if (u_sess->proc_cxt.MyProcPort->ssl == NULL)
PG_RETURN_NULL();
PG_RETURN_TEXT_P(cstring_to_text(SSL_get_version(u_sess->proc_cxt.MyProcPort->ssl)));
}
/*
* Returns SSL cipher currently in use.
*/
PG_FUNCTION_INFO_V1(ssl_cipher);
Datum ssl_cipher(PG_FUNCTION_ARGS)
{
if (u_sess->proc_cxt.MyProcPort->ssl == NULL)
PG_RETURN_NULL();
PG_RETURN_TEXT_P(cstring_to_text(SSL_get_cipher(u_sess->proc_cxt.MyProcPort->ssl)));
}
/*
* 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(u_sess->proc_cxt.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 = u_sess->proc_cxt.MyProcPort;
X509* peer = port->peer;
ASN1_INTEGER* serial = NULL;
BIGNUM* b = NULL;
char* decimal = NULL;
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 management
* 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;
char nullterm;
char* sp = NULL;
char* dp = NULL;
text* result = NULL;
membuf = BIO_new(BIO_s_mem());
(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 = (char*)pg_do_encoding_conversion((unsigned char*)sp, size - 1, PG_UTF8, GetDatabaseEncoding());
result = cstring_to_text(dp);
if (dp != sp)
pfree(dp);
BIO_free(membuf);
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* string_fieldname = NULL;
int nid, index;
ASN1_STRING* data = NULL;
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_P(0);
Datum result;
if (!(u_sess->proc_cxt.MyProcPort->peer))
PG_RETURN_NULL();
result = X509_NAME_field_to_text(X509_get_subject_name(u_sess->proc_cxt.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 (!(u_sess->proc_cxt.MyProcPort->peer))
PG_RETURN_NULL();
result = X509_NAME_field_to_text(X509_get_issuer_name(u_sess->proc_cxt.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 = NULL;
ASN1_STRING* v = NULL;
const char* field_name = NULL;
size_t size;
char nullterm;
char* sp = NULL;
char* dp = NULL;
text* result = NULL;
(void)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));
}
/* ensure null termination of the BIO's content */
nullterm = '\0';
BIO_write(membuf, &nullterm, 1);
size = BIO_get_mem_data(membuf, &sp);
dp = (char*)pg_do_encoding_conversion((unsigned char*)sp, size - 1, PG_UTF8, GetDatabaseEncoding());
result = cstring_to_text(dp);
if (dp != sp)
pfree(dp);
BIO_free(membuf);
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 (!(u_sess->proc_cxt.MyProcPort->peer))
PG_RETURN_NULL();
return X509_NAME_to_text(X509_get_subject_name(u_sess->proc_cxt.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 (!(u_sess->proc_cxt.MyProcPort->peer))
PG_RETURN_NULL();
return X509_NAME_to_text(X509_get_issuer_name(u_sess->proc_cxt.MyProcPort->peer));
}