Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b3ad0e5

Browse files
authored
bpo-28182: Expose OpenSSL verification results (#3412)
The SSL module now raises SSLCertVerificationError when OpenSSL fails to verify the peer's certificate. The exception contains more information about the error. Original patch by Chi Hsuan Yen Signed-off-by: Christian Heimes <[email protected]>
1 parent af8d6b9 commit b3ad0e5

5 files changed

Lines changed: 134 additions & 19 deletions

File tree

Doc/library/ssl.rst

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,26 @@ Functions, Constants, and Exceptions
129129

130130
.. versionadded:: 3.3
131131

132+
.. exception:: SSLCertVerificationError
133+
134+
A subclass of :exc:`SSLError` raised when certificate validation has
135+
failed.
136+
137+
.. versionadded:: 3.7
138+
139+
.. attribute:: verify_code
140+
141+
A numeric error number that denotes the verification error.
142+
143+
.. attribute:: verify_message
144+
145+
A human readable string of the verification error.
146+
132147
.. exception:: CertificateError
133148

134149
Raised to signal an error with a certificate (such as mismatching
135150
hostname). Certificate errors detected by OpenSSL, though, raise
136-
an :exc:`SSLError`.
151+
an :exc:`SSLCertVerificationError`.
137152

138153

139154
Socket creation

Lib/ssl.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@
104104
from _ssl import _SSLContext, MemoryBIO, SSLSession
105105
from _ssl import (
106106
SSLError, SSLZeroReturnError, SSLWantReadError, SSLWantWriteError,
107-
SSLSyscallError, SSLEOFError,
107+
SSLSyscallError, SSLEOFError, SSLCertVerificationError
108108
)
109109
from _ssl import txt2obj as _txt2obj, nid2obj as _nid2obj
110110
from _ssl import RAND_status, RAND_add, RAND_bytes, RAND_pseudo_bytes

Lib/test/test_ssl.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2530,6 +2530,29 @@ def connector():
25302530
finally:
25312531
t.join()
25322532

2533+
def test_ssl_cert_verify_error(self):
2534+
if support.verbose:
2535+
sys.stdout.write("\n")
2536+
2537+
server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
2538+
server_context.load_cert_chain(SIGNED_CERTFILE)
2539+
2540+
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
2541+
2542+
server = ThreadedEchoServer(context=server_context, chatty=True)
2543+
with server:
2544+
with context.wrap_socket(socket.socket(),
2545+
server_hostname="localhost") as s:
2546+
try:
2547+
s.connect((HOST, server.port))
2548+
except ssl.SSLError as e:
2549+
msg = 'unable to get local issuer certificate'
2550+
self.assertIsInstance(e, ssl.SSLCertVerificationError)
2551+
self.assertEqual(e.verify_code, 20)
2552+
self.assertEqual(e.verify_message, msg)
2553+
self.assertIn(msg, repr(e))
2554+
self.assertIn('certificate verify failed', repr(e))
2555+
25332556
@skip_if_broken_ubuntu_ssl
25342557
@unittest.skipUnless(hasattr(ssl, 'PROTOCOL_SSLv2'),
25352558
"OpenSSL is compiled without SSLv2 support")
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The SSL module now raises SSLCertVerificationError when OpenSSL fails to
2+
verify the peer's certificate. The exception contains more information about
3+
the error.

Modules/_ssl.c

Lines changed: 91 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ static PySocketModule_APIObject PySocketModule;
6666

6767
/* SSL error object */
6868
static PyObject *PySSLErrorObject;
69+
static PyObject *PySSLCertVerificationErrorObject;
6970
static PyObject *PySSLZeroReturnErrorObject;
7071
static PyObject *PySSLWantReadErrorObject;
7172
static PyObject *PySSLWantWriteErrorObject;
@@ -386,6 +387,9 @@ typedef enum {
386387
PyDoc_STRVAR(SSLError_doc,
387388
"An error occurred in the SSL implementation.");
388389

390+
PyDoc_STRVAR(SSLCertVerificationError_doc,
391+
"A certificate could not be verified.");
392+
389393
PyDoc_STRVAR(SSLZeroReturnError_doc,
390394
"SSL/TLS session closed cleanly.");
391395

@@ -430,13 +434,16 @@ static PyType_Spec sslerror_type_spec = {
430434
};
431435

432436
static void
433-
fill_and_set_sslerror(PyObject *type, int ssl_errno, const char *errstr,
434-
int lineno, unsigned long errcode)
437+
fill_and_set_sslerror(PySSLSocket *sslsock, PyObject *type, int ssl_errno,
438+
const char *errstr, int lineno, unsigned long errcode)
435439
{
436440
PyObject *err_value = NULL, *reason_obj = NULL, *lib_obj = NULL;
441+
PyObject *verify_obj = NULL, *verify_code_obj = NULL;
437442
PyObject *init_value, *msg, *key;
438443
_Py_IDENTIFIER(reason);
439444
_Py_IDENTIFIER(library);
445+
_Py_IDENTIFIER(verify_message);
446+
_Py_IDENTIFIER(verify_code);
440447

441448
if (errcode != 0) {
442449
int lib, reason;
@@ -466,7 +473,50 @@ fill_and_set_sslerror(PyObject *type, int ssl_errno, const char *errstr,
466473
if (errstr == NULL)
467474
errstr = "unknown error";
468475

469-
if (reason_obj && lib_obj)
476+
/* verify code for cert validation error */
477+
if ((sslsock != NULL) && (type == PySSLCertVerificationErrorObject)) {
478+
const char *verify_str = NULL;
479+
long verify_code;
480+
481+
verify_code = SSL_get_verify_result(sslsock->ssl);
482+
verify_code_obj = PyLong_FromLong(verify_code);
483+
if (verify_code_obj == NULL) {
484+
goto fail;
485+
}
486+
487+
switch (verify_code) {
488+
case X509_V_ERR_HOSTNAME_MISMATCH:
489+
verify_obj = PyUnicode_FromFormat(
490+
"Hostname mismatch, certificate is not valid for '%S'.",
491+
sslsock->server_hostname
492+
);
493+
break;
494+
case X509_V_ERR_IP_ADDRESS_MISMATCH:
495+
verify_obj = PyUnicode_FromFormat(
496+
"IP address mismatch, certificate is not valid for '%S'.",
497+
sslsock->server_hostname
498+
);
499+
break;
500+
default:
501+
verify_str = X509_verify_cert_error_string(verify_code);
502+
if (verify_str != NULL) {
503+
verify_obj = PyUnicode_FromString(verify_str);
504+
} else {
505+
verify_obj = Py_None;
506+
Py_INCREF(verify_obj);
507+
}
508+
break;
509+
}
510+
if (verify_obj == NULL) {
511+
goto fail;
512+
}
513+
}
514+
515+
if (verify_obj && reason_obj && lib_obj)
516+
msg = PyUnicode_FromFormat("[%S: %S] %s: %S (_ssl.c:%d)",
517+
lib_obj, reason_obj, errstr, verify_obj,
518+
lineno);
519+
else if (reason_obj && lib_obj)
470520
msg = PyUnicode_FromFormat("[%S: %S] %s (_ssl.c:%d)",
471521
lib_obj, reason_obj, errstr, lineno);
472522
else if (lib_obj)
@@ -490,17 +540,30 @@ fill_and_set_sslerror(PyObject *type, int ssl_errno, const char *errstr,
490540
reason_obj = Py_None;
491541
if (_PyObject_SetAttrId(err_value, &PyId_reason, reason_obj))
492542
goto fail;
543+
493544
if (lib_obj == NULL)
494545
lib_obj = Py_None;
495546
if (_PyObject_SetAttrId(err_value, &PyId_library, lib_obj))
496547
goto fail;
548+
549+
if ((sslsock != NULL) && (type == PySSLCertVerificationErrorObject)) {
550+
/* Only set verify code / message for SSLCertVerificationError */
551+
if (_PyObject_SetAttrId(err_value, &PyId_verify_code,
552+
verify_code_obj))
553+
goto fail;
554+
if (_PyObject_SetAttrId(err_value, &PyId_verify_message, verify_obj))
555+
goto fail;
556+
}
557+
497558
PyErr_SetObject(type, err_value);
498559
fail:
499560
Py_XDECREF(err_value);
561+
Py_XDECREF(verify_code_obj);
562+
Py_XDECREF(verify_obj);
500563
}
501564

502565
static PyObject *
503-
PySSL_SetError(PySSLSocket *obj, int ret, const char *filename, int lineno)
566+
PySSL_SetError(PySSLSocket *sslsock, int ret, const char *filename, int lineno)
504567
{
505568
PyObject *type = PySSLErrorObject;
506569
char *errstr = NULL;
@@ -511,8 +574,8 @@ PySSL_SetError(PySSLSocket *obj, int ret, const char *filename, int lineno)
511574
assert(ret <= 0);
512575
e = ERR_peek_last_error();
513576

514-
if (obj->ssl != NULL) {
515-
err = SSL_get_error(obj->ssl, ret);
577+
if (sslsock->ssl != NULL) {
578+
err = SSL_get_error(sslsock->ssl, ret);
516579

517580
switch (err) {
518581
case SSL_ERROR_ZERO_RETURN:
@@ -541,7 +604,7 @@ PySSL_SetError(PySSLSocket *obj, int ret, const char *filename, int lineno)
541604
case SSL_ERROR_SYSCALL:
542605
{
543606
if (e == 0) {
544-
PySocketSockObject *s = GET_SOCKET(obj);
607+
PySocketSockObject *s = GET_SOCKET(sslsock);
545608
if (ret == 0 || (((PyObject *)s) == Py_None)) {
546609
p = PY_SSL_ERROR_EOF;
547610
type = PySSLEOFErrorObject;
@@ -566,17 +629,22 @@ PySSL_SetError(PySSLSocket *obj, int ret, const char *filename, int lineno)
566629
case SSL_ERROR_SSL:
567630
{
568631
p = PY_SSL_ERROR_SSL;
569-
if (e == 0)
632+
if (e == 0) {
570633
/* possible? */
571634
errstr = "A failure in the SSL library occurred";
635+
}
636+
if (ERR_GET_LIB(e) == ERR_LIB_SSL &&
637+
ERR_GET_REASON(e) == SSL_R_CERTIFICATE_VERIFY_FAILED) {
638+
type = PySSLCertVerificationErrorObject;
639+
}
572640
break;
573641
}
574642
default:
575643
p = PY_SSL_ERROR_INVALID_ERROR_CODE;
576644
errstr = "Invalid error code";
577645
}
578646
}
579-
fill_and_set_sslerror(type, p, errstr, lineno, e);
647+
fill_and_set_sslerror(sslsock, type, p, errstr, lineno, e);
580648
ERR_clear_error();
581649
return NULL;
582650
}
@@ -588,15 +656,11 @@ _setSSLError (const char *errstr, int errcode, const char *filename, int lineno)
588656
errcode = ERR_peek_last_error();
589657
else
590658
errcode = 0;
591-
fill_and_set_sslerror(PySSLErrorObject, errcode, errstr, lineno, errcode);
659+
fill_and_set_sslerror(NULL, PySSLErrorObject, errcode, errstr, lineno, errcode);
592660
ERR_clear_error();
593661
return NULL;
594662
}
595663

596-
/*
597-
* SSL objects
598-
*/
599-
600664
static PySSLSocket *
601665
newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock,
602666
enum py_ssl_server_or_client socket_type,
@@ -656,7 +720,6 @@ newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock,
656720
if (server_hostname != NULL)
657721
SSL_set_tlsext_host_name(self->ssl, server_hostname);
658722
#endif
659-
660723
/* If the socket is in non-blocking mode or timeout mode, set the BIO
661724
* to non-blocking mode (blocking is the default)
662725
*/
@@ -5130,7 +5193,7 @@ parse_openssl_version(unsigned long libver,
51305193
PyMODINIT_FUNC
51315194
PyInit__ssl(void)
51325195
{
5133-
PyObject *m, *d, *r;
5196+
PyObject *m, *d, *r, *bases;
51345197
unsigned long libver;
51355198
unsigned int major, minor, fix, patch, status;
51365199
PySocketModule_APIObject *socket_api;
@@ -5182,6 +5245,14 @@ PyInit__ssl(void)
51825245
if (PySSLErrorObject == NULL)
51835246
return NULL;
51845247

5248+
/* ssl.CertificateError used to be a subclass of ValueError */
5249+
bases = Py_BuildValue("OO", PySSLErrorObject, PyExc_ValueError);
5250+
if (bases == NULL)
5251+
return NULL;
5252+
PySSLCertVerificationErrorObject = PyErr_NewExceptionWithDoc(
5253+
"ssl.SSLCertVerificationError", SSLCertVerificationError_doc,
5254+
bases, NULL);
5255+
Py_DECREF(bases);
51855256
PySSLZeroReturnErrorObject = PyErr_NewExceptionWithDoc(
51865257
"ssl.SSLZeroReturnError", SSLZeroReturnError_doc,
51875258
PySSLErrorObject, NULL);
@@ -5197,13 +5268,16 @@ PyInit__ssl(void)
51975268
PySSLEOFErrorObject = PyErr_NewExceptionWithDoc(
51985269
"ssl.SSLEOFError", SSLEOFError_doc,
51995270
PySSLErrorObject, NULL);
5200-
if (PySSLZeroReturnErrorObject == NULL
5271+
if (PySSLCertVerificationErrorObject == NULL
5272+
|| PySSLZeroReturnErrorObject == NULL
52015273
|| PySSLWantReadErrorObject == NULL
52025274
|| PySSLWantWriteErrorObject == NULL
52035275
|| PySSLSyscallErrorObject == NULL
52045276
|| PySSLEOFErrorObject == NULL)
52055277
return NULL;
52065278
if (PyDict_SetItemString(d, "SSLError", PySSLErrorObject) != 0
5279+
|| PyDict_SetItemString(d, "SSLCertVerificationError",
5280+
PySSLCertVerificationErrorObject) != 0
52075281
|| PyDict_SetItemString(d, "SSLZeroReturnError", PySSLZeroReturnErrorObject) != 0
52085282
|| PyDict_SetItemString(d, "SSLWantReadError", PySSLWantReadErrorObject) != 0
52095283
|| PyDict_SetItemString(d, "SSLWantWriteError", PySSLWantWriteErrorObject) != 0

0 commit comments

Comments
 (0)