1
0
mirror of https://github.com/mariadb-corporation/mariadb-connector-c.git synced 2025-08-08 14:02:17 +03:00

CONC-692: Provide X509 peer certificate information

Added a new structure MARIADB_X509_INFO, which
contains information about servers certificate.
The information can be obtained via mysql_get_infov API
function:

MARIADB_X509_INFO *info;
mariadb_get_infov(mysql, MARIADB_TLS_PEER_CERT_INFO, &info);
This commit is contained in:
Georg Richter
2024-04-24 11:21:28 +02:00
parent fef3e4ed6d
commit 19dffea4dc
8 changed files with 226 additions and 10 deletions

View File

@@ -27,6 +27,7 @@ typedef struct st_ma_pvio_tls {
void *data;
MARIADB_PVIO *pvio;
void *ssl;
MARIADB_X509_INFO cert_info;
} MARIADB_TLS;
/* Function prototypes */

View File

@@ -33,6 +33,7 @@ extern "C" {
#endif
#include <stdarg.h>
#include <time.h>
#if !defined (_global_h) && !defined (MY_GLOBAL_INCLUDED) /* If not standard header */
#include <sys/types.h>
@@ -297,7 +298,8 @@ extern const char *SQLSTATE_UNKNOWN;
MARIADB_CONNECTION_EXTENDED_SERVER_CAPABILITIES,
MARIADB_CONNECTION_CLIENT_CAPABILITIES,
MARIADB_CONNECTION_BYTES_READ,
MARIADB_CONNECTION_BYTES_SENT
MARIADB_CONNECTION_BYTES_SENT,
MARIADB_TLS_PEER_CERT_INFO,
};
enum mysql_status { MYSQL_STATUS_READY,
@@ -481,6 +483,27 @@ struct st_mysql_client_plugin
MYSQL_CLIENT_PLUGIN_HEADER
};
enum mariadb_tls_verification {
MARIADB_VERIFY_NONE = 0,
MARIADB_VERIFY_PIPE,
MARIADB_VERIFY_UNIXSOCKET,
MARIADB_VERIFY_LOCALHOST,
MARIADB_VERIFY_FINGERPRINT,
MARIADB_VERIFY_PEER_CERT
};
typedef struct
{
int version;
char *issuer;
char *subject;
char fingerprint[65];
struct tm not_before;
struct tm not_after;
enum mariadb_tls_verification verify_mode;
} MARIADB_X509_INFO;
struct st_mysql_client_plugin *
mysql_load_plugin(struct st_mysql *mysql, const char *name, int type,
int argc, ...);

View File

@@ -549,13 +549,24 @@ static my_bool ignore_self_signed_cert_error(MARIADB_PVIO *pvio)
"127.0.0.1", "::1", NULL};
int i;
if (pvio->type != PVIO_TYPE_SOCKET)
{
pvio->ctls->cert_info.verify_mode=
#ifdef WIN32
MARIADB_VERIFY_PIPE;
#else
MARIADB_VERIFY_UNIXSOCKET;
#endif
return TRUE;
}
if (!hostname)
return FALSE;
for (i= 0; local_host_names[i]; i++)
{
if (strcmp(hostname, local_host_names[i]) == 0)
{
pvio->ctls->cert_info.verify_mode= MARIADB_VERIFY_LOCALHOST;
return TRUE;
}
}
return FALSE;
}
@@ -586,6 +597,8 @@ my_bool ma_pvio_start_ssl(MARIADB_PVIO *pvio)
!pvio->mysql->net.tls_self_signed_error &&
ma_pvio_tls_verify_server_cert(pvio->ctls))
return 1;
else
pvio->ctls->cert_info.verify_mode= MARIADB_VERIFY_PEER_CERT;
if (pvio->mysql->options.extension &&
((pvio->mysql->options.extension->tls_fp && pvio->mysql->options.extension->tls_fp[0]) ||
@@ -595,6 +608,7 @@ my_bool ma_pvio_start_ssl(MARIADB_PVIO *pvio)
pvio->mysql->options.extension->tls_fp,
pvio->mysql->options.extension->tls_fp_list))
return 1;
pvio->ctls->cert_info.verify_mode= MARIADB_VERIFY_FINGERPRINT;
reset_tls_self_signed_error(pvio->mysql); // validated
}

View File

@@ -4519,6 +4519,11 @@ my_bool mariadb_get_infov(MYSQL *mysql, enum mariadb_value value, void *arg, ...
va_start(ap, arg);
switch(value) {
#ifdef HAVE_TLS
case MARIADB_TLS_PEER_CERT_INFO:
*((MARIADB_X509_INFO **)arg)= mysql->net.pvio->ctls ? (MARIADB_X509_INFO *)&mysql->net.pvio->ctls->cert_info : NULL;
break;
#endif
case MARIADB_MAX_ALLOWED_PACKET:
*((size_t *)arg)= (size_t)max_allowed_packet;
break;

View File

@@ -1173,6 +1173,8 @@ my_bool ma_tls_connect(MARIADB_TLS *ctls)
MYSQL *mysql= (MYSQL *)gnutls_session_get_ptr(ssl);
MARIADB_PVIO *pvio;
int ret;
const gnutls_datum_t *cert_list;
unsigned int list_size= 0;
if (!mysql)
return 1;
@@ -1214,6 +1216,39 @@ my_bool ma_tls_connect(MARIADB_TLS *ctls)
return 1;
}
ctls->ssl= (void *)ssl;
/* retrieve peer certificate information */
if ((cert_list= gnutls_certificate_get_peers(ssl, &list_size)))
{
gnutls_x509_crt_t cert;
gnutls_x509_crt_init(&cert);
memset(&ctls->cert_info, 0, sizeof(MARIADB_X509_INFO));
if (!gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER))
{
size_t len= 0;
time_t notBefore, notAfter;
ctls->cert_info.version= gnutls_x509_crt_get_version(cert);
gnutls_x509_crt_get_issuer_dn(cert, NULL, &len);
if ((ctls->cert_info.issuer= (char *)malloc(len)))
gnutls_x509_crt_get_issuer_dn(cert, ctls->cert_info.issuer, &len);
gnutls_x509_crt_get_dn(cert, NULL, &len);
if ((ctls->cert_info.subject= (char *)malloc(len)))
gnutls_x509_crt_get_dn(cert, ctls->cert_info.subject, &len);
notBefore= gnutls_x509_crt_get_activation_time(cert);
memcpy(&ctls->cert_info.not_before, gmtime(&notBefore), sizeof(struct tm));
notAfter= gnutls_x509_crt_get_expiration_time(cert);
memcpy(&ctls->cert_info.not_after, gmtime(&notAfter), sizeof(struct tm));
ma_tls_get_finger_print(ctls, MA_HASH_SHA256, (char *)&ctls->cert_info.fingerprint, 32);
}
}
return 0;
}
@@ -1321,6 +1356,8 @@ my_bool ma_tls_close(MARIADB_TLS *ctls)
gnutls_certificate_free_ca_names(ctx);
gnutls_certificate_free_credentials(ctx);
gnutls_deinit((gnutls_session_t )ctls->ssl);
free(ctls->cert_info.issuer);
free(ctls->cert_info.subject);
ctls->ssl= NULL;
}
return 0;

View File

@@ -463,6 +463,7 @@ my_bool ma_tls_connect(MARIADB_TLS *ctls)
MYSQL *mysql;
MARIADB_PVIO *pvio;
int rc;
X509 *cert;
#ifdef OPENSSL_USE_BIOMETHOD
BIO_METHOD *bio_method= NULL;
BIO *bio;
@@ -529,6 +530,23 @@ my_bool ma_tls_connect(MARIADB_TLS *ctls)
}
pvio->ctls->ssl= ctls->ssl= (void *)ssl;
/* Store peer certificate information */
if ((cert= SSL_get_peer_certificate(ssl)))
{
char fp[33];
const ASN1_TIME *not_before= X509_get0_notBefore(cert),
*not_after= X509_get0_notAfter(cert);
ctls->cert_info.subject= X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0);
ctls->cert_info.issuer= X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0);
ctls->cert_info.version= X509_get_version(cert) + 1;
ASN1_TIME_to_tm(not_before, (struct tm *)&ctls->cert_info.not_before);
ASN1_TIME_to_tm(not_after, (struct tm *)&ctls->cert_info.not_after);
ma_tls_get_finger_print(ctls, MA_HASH_SHA256, fp, 33);
mysql_hex_string(ctls->cert_info.fingerprint, fp, 32);
}
return 0;
}
@@ -654,6 +672,9 @@ my_bool ma_tls_close(MARIADB_TLS *ctls)
SSL_free(ssl);
ctls->ssl= NULL;
OPENSSL_free(ctls->cert_info.issuer);
OPENSSL_free(ctls->cert_info.subject);
return rc;
}
@@ -739,33 +760,30 @@ unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, uint hash_type, char *fp
MYSQL *mysql;
unsigned int fp_len;
const EVP_MD *hash_alg;
unsigned int max_len= EVP_MAX_MD_SIZE;
if (!ctls || !ctls->ssl)
return 0;
mysql = SSL_get_app_data(ctls->ssl);
if (len < EVP_MAX_MD_SIZE)
{
my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN,
ER(CR_SSL_CONNECTION_ERROR),
"Finger print buffer too small");
return 0;
}
switch (hash_type)
{
case MA_HASH_SHA1:
hash_alg = EVP_sha1();
max_len= 20;
break;
case MA_HASH_SHA224:
hash_alg = EVP_sha224();
max_len= 28;
break;
case MA_HASH_SHA256:
hash_alg = EVP_sha256();
max_len= 32;
break;
case MA_HASH_SHA384:
hash_alg = EVP_sha384();
max_len= 48;
break;
case MA_HASH_SHA512:
hash_alg = EVP_sha512();
@@ -777,6 +795,14 @@ unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, uint hash_type, char *fp
return 0;
}
if (len < max_len)
{
my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN,
ER(CR_SSL_CONNECTION_ERROR),
"Finger print buffer too small");
return 0;
}
if (!(cert= SSL_get_peer_certificate(ctls->ssl)))
{
my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN,

View File

@@ -32,6 +32,8 @@ char tls_library_version[] = "Schannel";
#define PROT_TLS1_2 4
#define PROT_TLS1_3 8
unsigned int ma_set_tls_x509_info(MARIADB_TLS *ctls);
static struct
{
DWORD cipher_id;
@@ -457,6 +459,7 @@ my_bool ma_tls_connect(MARIADB_TLS *ctls)
goto end;
}
ma_set_tls_x509_info(ctls);
rc = 0;
end:
@@ -511,6 +514,8 @@ my_bool ma_tls_close(MARIADB_TLS *ctls)
DeleteSecurityContext(&sctx->hCtxt);
}
LocalFree(sctx);
LocalFree(ctls->cert_info.issuer);
LocalFree(ctls->cert_info.subject);
return 0;
}
/* }}} */
@@ -551,6 +556,60 @@ const char *ma_tls_get_cipher(MARIADB_TLS *ctls)
return cipher_name(&CipherInfo);
}
unsigned char *ma_cert_blob_to_str(PCERT_NAME_BLOB cnblob)
{
DWORD type= CERT_X500_NAME_STR;
DWORD size= CertNameToStrA(X509_ASN_ENCODING, cnblob, type, NULL, 0);
char *str= NULL;
if (!size)
return NULL;
str= (char *)LocalAlloc(LMEM_ZEROINIT,size);
CertNameToStrA(X509_ASN_ENCODING, cnblob, type, str, size);
return str;
}
static void ma_systime_to_tm(SYSTEMTIME sys_tm, struct tm *tm)
{
memset(tm, 0, sizeof(struct tm));
tm->tm_year= sys_tm.wYear - 1900;
tm->tm_mon= sys_tm.wMonth - 1;
tm->tm_mday= sys_tm.wDay;
tm->tm_hour = sys_tm.wHour;
tm->tm_min = sys_tm.wMinute;
}
unsigned int ma_set_tls_x509_info(MARIADB_TLS *ctls)
{
PCCERT_CONTEXT pCertCtx= NULL;
SC_CTX *sctx= (SC_CTX *)ctls->ssl;
PCERT_INFO pci= NULL;
DWORD size´= 0;
SYSTEMTIME tm;
char fp[33];
if (QueryContextAttributes(&sctx->hCtxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pCertCtx) != SEC_E_OK)
return 1;
pci= pCertCtx->pCertInfo;
ctls->cert_info.version= pci->dwVersion;
ctls->cert_info.subject = ma_cert_blob_to_str(&pci->Subject);
ctls->cert_info.issuer = ma_cert_blob_to_str(&pci->Issuer);
FileTimeToSystemTime(&pci->NotBefore, &tm);
ma_systime_to_tm(tm, &ctls->cert_info.not_before);
FileTimeToSystemTime(&pci->NotAfter, &tm);
ma_systime_to_tm(tm, &ctls->cert_info.not_after);
ma_tls_get_finger_print(ctls, MA_HASH_SHA256, fp, 33);
mysql_hex_string(ctls->cert_info.fingerprint, fp, 32);
return 0;
}
unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, uint hash_type, char *fp, unsigned int len)
{
MA_HASH_CTX* hash_ctx;

View File

@@ -1369,6 +1369,7 @@ static int test_conc276(MYSQL *unused __attribute__((unused)))
MYSQL *mysql= mysql_init(NULL);
int rc;
my_bool val= 1;
MARIADB_X509_INFO *info;
mysql_options(mysql, MYSQL_OPT_SSL_ENFORCE, &val);
mysql_options(mysql, MYSQL_OPT_RECONNECT, &val);
@@ -1380,7 +1381,15 @@ static int test_conc276(MYSQL *unused __attribute__((unused)))
return FAIL;
}
diag("Cipher in use: %s", mysql_get_ssl_cipher(mysql));
mariadb_get_infov(mysql, MARIADB_TLS_PEER_CERT_INFO, &info);
diag("subject: %s", info->subject);
diag("issuer: %s", info->issuer);
diag("fingerprint: %s", info->fingerprint);
diag("not before : %04d.%02d.%02d", info->not_before.tm_year + 1900,
info->not_before.tm_mon + 1, info->not_before.tm_mday);
diag("not after : %04d.%02d.%02d", info->not_after.tm_year + 1900,
info->not_after.tm_mon + 1, info->not_after.tm_mday);
rc= mariadb_reconnect(mysql);
check_mysql_rc(rc, mysql);
@@ -2309,7 +2318,49 @@ static int test_conc632(MYSQL *my __attribute__((unused)))
return OK;
}
static int test_x509(MYSQL *my __attribute__((unused)))
{
MYSQL *mysql1, *mysql2;
my_bool val= 1;
my_bool verify= 0;
char fp[65];
MARIADB_X509_INFO *info;
mysql1= mysql_init(NULL);
mysql2= mysql_init(NULL);
mysql_options(mysql1, MYSQL_OPT_SSL_ENFORCE, &val);
mysql_options(mysql2, MYSQL_OPT_SSL_ENFORCE, &val);
mysql_options(mysql1, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, &verify);
if (!(my_test_connect(mysql1, hostname, username,
password, schema, port,
socketname, 0)))
{
diag("connection failed");
return FAIL;
}
mariadb_get_infov(mysql1, MARIADB_TLS_PEER_CERT_INFO, &info);
memset(fp, 0, 65);
diag("fingerprint: %s", info->fingerprint);
mysql_options(mysql2, MARIADB_OPT_TLS_PEER_FP, info->fingerprint);
if (!(my_test_connect(mysql2, hostname, username,
password, schema, port,
socketname, 0)))
{
diag("connection failed");
return FAIL;
}
mariadb_get_infov(mysql2, MARIADB_TLS_PEER_CERT_INFO, &info);
FAIL_IF(info->verify_mode != MARIADB_VERIFY_FINGERPRINT, "Fingerprint verification expected");
mysql_close(mysql1);
mysql_close(mysql2);
return OK;
}
struct my_tests_st my_tests[] = {
{"test_x509", test_x509, TEST_CONNECTION_NONE, 0, NULL, NULL},
{"test_conc632", test_conc632, TEST_CONNECTION_NONE, 0, NULL, NULL},
{"test_status_callback", test_status_callback, TEST_CONNECTION_NONE, 0, NULL, NULL},
{"test_conc365", test_conc365, TEST_CONNECTION_NONE, 0, NULL, NULL},