Skip to content

Commit fee7f30

Browse files
committed
Added new connection property: tls_peer_cert_info
connection.tls_peer_cert_info returns information about the peer certificate or None if the connection doesn't use TLS/SSL.
1 parent 3d34bb6 commit fee7f30

File tree

6 files changed

+222
-36
lines changed

6 files changed

+222
-36
lines changed

include/mariadb_python.h

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,48 @@ typedef CRITICAL_SECTION pthread_mutex_t;
6060
#include <limits.h>
6161
#endif /* defined(_WIN32) */
6262

63+
enum mariadb_info
64+
{
65+
PYMARIADB_CHARSET_ID,
66+
PYMARIADB_CHARSET_NAME,
67+
PYMARIADB_CLIENT_ERRORS,
68+
PYMARIADB_CLIENT_VERSION,
69+
PYMARIADB_CLIENT_VERSION_ID,
70+
PYMARIADB_CONNECTION_ASYNC_TIMEOUT,
71+
PYMARIADB_CONNECTION_ASYNC_TIMEOUT_MS,
72+
PYMARIADB_CONNECTION_MARIADB_CHARSET_INFO,
73+
PYMARIADB_CONNECTION_ERROR,
74+
PYMARIADB_CONNECTION_ERROR_ID,
75+
PYMARIADB_CONNECTION_HOST,
76+
PYMARIADB_CONNECTION_INFO,
77+
PYMARIADB_CONNECTION_PORT,
78+
PYMARIADB_CONNECTION_PROTOCOL_VERSION_ID,
79+
PYMARIADB_CONNECTION_PVIO_TYPE,
80+
PYMARIADB_CONNECTION_SCHEMA,
81+
PYMARIADB_CONNECTION_SERVER_TYPE,
82+
PYMARIADB_CONNECTION_SERVER_VERSION,
83+
PYMARIADB_CONNECTION_SERVER_VERSION_ID,
84+
PYMARIADB_CONNECTION_SOCKET,
85+
PYMARIADB_CONNECTION_SQLSTATE,
86+
PYMARIADB_CONNECTION_SSL_CIPHER,
87+
PYMARIADB_TLS_LIBRARY,
88+
PYMARIADB_CONNECTION_TLS_VERSION,
89+
PYMARIADB_CONNECTION_TLS_VERSION_ID,
90+
PYMARIADB_CONNECTION_TYPE,
91+
PYMARIADB_CONNECTION_UNIX_SOCKET,
92+
PYMARIADB_CONNECTION_USER,
93+
PYMARIADB_MAX_ALLOWED_PACKET,
94+
PYMARIADB_NET_BUFFER_LENGTH,
95+
PYMARIADB_CONNECTION_SERVER_STATUS,
96+
PYMARIADB_CONNECTION_SERVER_CAPABILITIES,
97+
PYMARIADB_CONNECTION_EXTENDED_SERVER_CAPABILITIES,
98+
PYMARIADB_CONNECTION_CLIENT_CAPABILITIES,
99+
PYMARIADB_CONNECTION_BYTES_READ,
100+
PYMARIADB_CONNECTION_BYTES_SENT,
101+
PYMARIADB_TLS_PEER_CERT_INFO,
102+
PYMARIADB_TLS_VERIFY_STATUS
103+
};
104+
63105

64106
#ifndef MIN
65107
#define MIN(a,b) (a) < (b) ? (a) : (b)

mariadb/connections.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,15 @@ def tpc_recover(self):
474474
del cursor
475475
return result
476476

477+
@property
478+
def tls_peer_cert_info(self):
479+
"""Get peer certificate information."""
480+
481+
self._check_closed()
482+
if self._tls:
483+
return self._mariadb_get_info(INFO.TLS_PEER_CERT_INFO)
484+
return None
485+
477486
@property
478487
def database(self):
479488
"""Get default database for connection."""
@@ -592,6 +601,15 @@ def tls_version(self):
592601
return self._mariadb_get_info(INFO.TLS_VERSION)
593602
return None
594603

604+
@property
605+
def _tls_verify_status(self):
606+
"""Returns the result of the peer certificate verification."""
607+
608+
self._check_closed()
609+
if self._tls:
610+
return self._mariadb_get_info(INFO.TLS_VERIFY_STATUS)
611+
return None
612+
595613
@property
596614
def server_status(self):
597615
"""

mariadb/constants/INFO.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,5 @@
3838
CLIENT_CAPABILITIES = 33
3939
BYTES_READ = 34
4040
BYTES_SENT = 35
41+
TLS_PEER_CERT_INFO = 36
42+
TLS_VERIFY_STATUS = 37

mariadb/mariadb.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <datetime.h>
2626

2727
extern int codecs_datetime_init(void);
28+
extern int connection_datetime_init(void);
2829

2930
PyObject *decimal_module= NULL,
3031
*decimal_type= NULL,
@@ -108,6 +109,7 @@ PyMODINIT_FUNC PyInit__mariadb(void)
108109

109110
/* Initialize DateTimeAPI */
110111
if (mariadb_datetime_init() ||
112+
connection_datetime_init() ||
111113
codecs_datetime_init())
112114
{
113115
goto error;

mariadb/mariadb_connection.c

Lines changed: 126 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "mariadb_python.h"
2121
#include "docs/connection.h"
2222
#include "docs/exception.h"
23+
#include <datetime.h>
2324

2425
#define MADB_SET_OPTION(m,o,v)\
2526
if (mysql_optionsv((m), (o), (v)))\
@@ -40,6 +41,7 @@ char *dsn_keys[]= {
4041
"pool_reset_connection", "plugin_dir",
4142
"username", "db", "passwd",
4243
"status_callback", "tls_version",
44+
"tls_fp", "tls_fp_list",
4345
NULL
4446
};
4547

@@ -193,6 +195,19 @@ PyMemberDef MrdbConnection_Members[] =
193195
"Indicates if connection uses TLS/SSL"},
194196
{NULL} /* always last */
195197
};
198+
199+
int connection_datetime_init(void)
200+
{
201+
PyDateTime_IMPORT;
202+
203+
if (!PyDateTimeAPI) {
204+
PyErr_SetString(PyExc_ImportError, "DateTimeAPI initialization failed");
205+
return 1;
206+
}
207+
return 0;
208+
}
209+
210+
196211
#if MARIADB_PACKAGE_VERSION_ID > 30301
197212
void MrdbConnection_process_status_info(void *data, enum enum_mariadb_status_info type, ...)
198213
{
@@ -296,7 +311,7 @@ MrdbConnection_Initialize(MrdbConnection *self,
296311
*default_group= NULL,
297312
*ssl_key= NULL, *ssl_cert= NULL, *ssl_ca= NULL, *ssl_capath= NULL,
298313
*ssl_crl= NULL, *ssl_crlpath= NULL, *ssl_cipher= NULL,
299-
*plugin_dir= NULL, *tls_version= NULL;
314+
*plugin_dir= NULL, *tls_version= NULL, *tls_fp= NULL, *tls_fp_list= NULL;
300315
char *pool_name= 0;
301316
uint32_t pool_size= 0;
302317
uint8_t ssl_enforce= 0;
@@ -308,7 +323,7 @@ MrdbConnection_Initialize(MrdbConnection *self,
308323
PyObject *status_callback= NULL;
309324

310325
if (!PyArg_ParseTupleAndKeywords(args, dsnargs,
311-
"|zzzzziziiibbzzzzzzzzzzibizibzzzzOz:connect",
326+
"|zzzzziziiibbzzzzzzzzzzibizibzzzzOzzz:connect",
312327
dsn_keys,
313328
&dsn, &host, &user, &password, &schema, &port, &socket,
314329
&connect_timeout, &read_timeout, &write_timeout,
@@ -320,7 +335,7 @@ MrdbConnection_Initialize(MrdbConnection *self,
320335
&client_flags, &pool_name, &pool_size,
321336
&reset_session, &plugin_dir,
322337
&user, &schema, &password, &status_callback,
323-
&tls_version))
338+
&tls_version, &tls_fp, &tls_fp_list))
324339
{
325340
return -1;
326341
}
@@ -424,7 +439,8 @@ MrdbConnection_Initialize(MrdbConnection *self,
424439
}
425440

426441
/* set TLS/SSL options */
427-
if (ssl_enforce || ssl_key || ssl_ca || ssl_cert || ssl_capath || ssl_cipher || tls_version)
442+
if (ssl_enforce || ssl_key || ssl_ca || ssl_cert || ssl_capath || ssl_cipher || tls_version ||
443+
tls_fp || tls_fp_list)
428444
mysql_ssl_set(self->mysql, (const char *)ssl_key,
429445
(const char *)ssl_cert,
430446
(const char *)ssl_ca,
@@ -447,6 +463,16 @@ MrdbConnection_Initialize(MrdbConnection *self,
447463
if (mysql_options(self->mysql, MARIADB_OPT_TLS_VERSION, tls_version))
448464
goto end;
449465
}
466+
if (tls_fp)
467+
{
468+
if (mysql_options(self->mysql, MARIADB_OPT_SSL_FP, tls_fp))
469+
goto end;
470+
}
471+
if (tls_fp_list)
472+
{
473+
if (mysql_options(self->mysql, MARIADB_OPT_SSL_FP_LIST, tls_fp_list))
474+
goto end;
475+
}
450476

451477
mysql_real_connect(self->mysql, host, user, password, schema, port,
452478
socket, client_flags);
@@ -681,13 +707,67 @@ static int MrdbConnection_setreconnect(MrdbConnection *self,
681707
}
682708
/* }}} */
683709

710+
static PyObject *
711+
MrdbConnection_X509info(MARIADB_X509_INFO *info)
712+
{
713+
PyObject *dict, *key, *val;
714+
struct tm *tmp;
715+
if (!info)
716+
Py_RETURN_NONE;
717+
718+
dict= PyDict_New();
719+
720+
key= PyUnicode_FromString("version");
721+
val= PyLong_FromLong((long)info->version);
722+
PyDict_SetItem(dict, key, val);
723+
Py_DECREF(key);
724+
Py_DECREF(val);
725+
726+
key= PyUnicode_FromString("subject");
727+
val= PyUnicode_FromString(info->subject);
728+
PyDict_SetItem(dict, key, val);
729+
Py_DECREF(key);
730+
Py_DECREF(val);
731+
732+
key= PyUnicode_FromString("issuer");
733+
val= PyUnicode_FromString(info->issuer);
734+
PyDict_SetItem(dict, key, val);
735+
Py_DECREF(key);
736+
Py_DECREF(val);
737+
738+
key= PyUnicode_FromString("fingerprint");
739+
val= PyUnicode_FromString(info->fingerprint);
740+
PyDict_SetItem(dict, key, val);
741+
Py_DECREF(key);
742+
Py_DECREF(val);
743+
744+
tmp= &info->not_before;
745+
key= PyUnicode_FromString("not_before");
746+
val= PyDateTime_FromDateAndTime(tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday,
747+
tmp->tm_hour, tmp->tm_min, tmp->tm_sec, 0);
748+
PyDict_SetItem(dict, key, val);
749+
Py_DECREF(key);
750+
Py_DECREF(val);
751+
752+
tmp= &info->not_after;
753+
key= PyUnicode_FromString("not_after");
754+
val= PyDateTime_FromDateAndTime(tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday,
755+
tmp->tm_hour, tmp->tm_min, tmp->tm_sec, 0);
756+
PyDict_SetItem(dict, key, val);
757+
Py_DECREF(key);
758+
Py_DECREF(val);
759+
760+
return dict;
761+
}
762+
684763
static PyObject *
685764
MrdbConnection_getinfo(MrdbConnection *self, PyObject *optionval)
686765
{
687766
union {
688767
char *str;
689768
uint64_t num;
690769
uint8_t b;
770+
void *ptr;
691771
} val;
692772

693773
uint32_t option;
@@ -709,43 +789,53 @@ MrdbConnection_getinfo(MrdbConnection *self, PyObject *optionval)
709789
}
710790

711791
switch (option) {
712-
case MARIADB_CONNECTION_UNIX_SOCKET:
713-
case MARIADB_CONNECTION_USER:
714-
case MARIADB_CHARSET_NAME:
715-
case MARIADB_TLS_LIBRARY:
716-
case MARIADB_CLIENT_VERSION:
717-
case MARIADB_CONNECTION_HOST:
718-
case MARIADB_CONNECTION_INFO:
719-
case MARIADB_CONNECTION_SCHEMA:
720-
case MARIADB_CONNECTION_SQLSTATE:
721-
case MARIADB_CONNECTION_SOCKET:
722-
case MARIADB_CONNECTION_SSL_CIPHER:
723-
case MARIADB_CONNECTION_TLS_VERSION:
724-
case MARIADB_CONNECTION_SERVER_VERSION:
792+
case PYMARIADB_CONNECTION_UNIX_SOCKET:
793+
case PYMARIADB_CONNECTION_USER:
794+
case PYMARIADB_CHARSET_NAME:
795+
case PYMARIADB_TLS_LIBRARY:
796+
case PYMARIADB_CLIENT_VERSION:
797+
case PYMARIADB_CONNECTION_HOST:
798+
case PYMARIADB_CONNECTION_INFO:
799+
case PYMARIADB_CONNECTION_SCHEMA:
800+
case PYMARIADB_CONNECTION_SQLSTATE:
801+
case PYMARIADB_CONNECTION_SOCKET:
802+
case PYMARIADB_CONNECTION_SSL_CIPHER:
803+
case PYMARIADB_CONNECTION_TLS_VERSION:
804+
case PYMARIADB_CONNECTION_SERVER_VERSION:
725805
return PyUnicode_FromString(val.str ? val.str : "");
726806
break;
727807

728-
case MARIADB_CHARSET_ID:
729-
case MARIADB_CLIENT_VERSION_ID:
730-
case MARIADB_CONNECTION_ASYNC_TIMEOUT:
731-
case MARIADB_CONNECTION_ASYNC_TIMEOUT_MS:
732-
case MARIADB_CONNECTION_PORT:
733-
case MARIADB_CONNECTION_PROTOCOL_VERSION_ID:
734-
case MARIADB_CONNECTION_SERVER_TYPE:
735-
case MARIADB_CONNECTION_SERVER_VERSION_ID:
736-
case MARIADB_CONNECTION_TLS_VERSION_ID:
737-
case MARIADB_MAX_ALLOWED_PACKET:
738-
case MARIADB_NET_BUFFER_LENGTH:
739-
case MARIADB_CONNECTION_SERVER_STATUS:
740-
case MARIADB_CONNECTION_SERVER_CAPABILITIES:
741-
case MARIADB_CONNECTION_EXTENDED_SERVER_CAPABILITIES:
742-
case MARIADB_CONNECTION_CLIENT_CAPABILITIES:
743-
#ifdef MARIADB_CONNECTION_BYTES_READ
744-
case MARIADB_CONNECTION_BYTES_READ:
745-
case MARIADB_CONNECTION_BYTES_SENT:
746-
#endif
808+
case PYMARIADB_CHARSET_ID:
809+
case PYMARIADB_CLIENT_VERSION_ID:
810+
case PYMARIADB_CONNECTION_ASYNC_TIMEOUT:
811+
case PYMARIADB_CONNECTION_ASYNC_TIMEOUT_MS:
812+
case PYMARIADB_CONNECTION_PORT:
813+
case PYMARIADB_CONNECTION_PROTOCOL_VERSION_ID:
814+
case PYMARIADB_CONNECTION_SERVER_TYPE:
815+
case PYMARIADB_CONNECTION_SERVER_VERSION_ID:
816+
case PYMARIADB_CONNECTION_TLS_VERSION_ID:
817+
case PYMARIADB_MAX_ALLOWED_PACKET:
818+
case PYMARIADB_NET_BUFFER_LENGTH:
819+
case PYMARIADB_CONNECTION_SERVER_STATUS:
820+
case PYMARIADB_CONNECTION_SERVER_CAPABILITIES:
821+
case PYMARIADB_CONNECTION_EXTENDED_SERVER_CAPABILITIES:
822+
case PYMARIADB_CONNECTION_CLIENT_CAPABILITIES:
823+
case PYMARIADB_CONNECTION_BYTES_READ:
824+
case PYMARIADB_CONNECTION_BYTES_SENT:
825+
case PYMARIADB_TLS_VERIFY_STATUS:
747826
return PyLong_FromLong((long)val.num);
748827
break;
828+
case PYMARIADB_TLS_PEER_CERT_INFO:
829+
{
830+
MARIADB_X509_INFO *info;
831+
832+
if (!self->tls_in_use)
833+
Py_RETURN_NONE;
834+
835+
mariadb_get_infov(self->mysql, option, &info, 256);
836+
return MrdbConnection_X509info(info);
837+
break;
838+
}
749839
default:
750840
Py_RETURN_NONE;
751841
}

testing/test/integration/test_connection.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,38 @@ def test_multi_host(self):
273273
parse_version('3.3.0'))
274274
pass
275275

276+
def test_tls_verification(self):
277+
default_conf= conf()
278+
default_conf["ssl"] = False
279+
conn= mariadb.connect(**default_conf)
280+
self.assertEqual(conn._tls_verify_status, None)
281+
conn.close()
282+
default_conf= conf()
283+
default_conf["ssl"] = True
284+
conn= mariadb.connect(**default_conf)
285+
self.assertNotEqual(conn._tls_verify_status, None)
286+
conn.close()
287+
288+
def test_tls_fp(self):
289+
default_conf= conf()
290+
default_conf["ssl"] = True
291+
conn= mariadb.connect(**default_conf)
292+
self.assertEqual(conn._tls, True)
293+
x509_info= conn.tls_peer_cert_info
294+
if not x509_info:
295+
conn.close()
296+
self.skipTest("Peer certificate information not supported")
297+
fp= x509_info["fingerprint"]
298+
self.assertEqual(len(fp), 64)
299+
conn.close()
300+
default_conf= conf()
301+
default_conf["tls_fp"] = fp
302+
conn= mariadb.connect(**default_conf)
303+
self.assertEqual(conn._tls, True)
304+
x509_info= conn.tls_peer_cert_info
305+
self.assertEqual(fp, x509_info["fingerprint"])
306+
conn.close()
307+
276308
def test_conpy278(self):
277309
if is_maxscale():
278310
self.skipTest("MAXSCALE bug MXS-4961")

0 commit comments

Comments
 (0)