diff options
-rw-r--r-- | Doc/library/ssl.rst | 45 | ||||
-rw-r--r-- | Lib/ssl.py | 2 | ||||
-rw-r--r-- | Lib/test/make_ssl_certs.py | 6 | ||||
-rw-r--r-- | Lib/test/revocation.crl | 11 | ||||
-rw-r--r-- | Lib/test/test_ssl.py | 63 | ||||
-rw-r--r-- | Misc/NEWS | 4 | ||||
-rw-r--r-- | Modules/_ssl.c | 49 |
7 files changed, 179 insertions, 1 deletions
diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index ce37c95..99386c0 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -423,6 +423,38 @@ Constants be passed, either to :meth:`SSLContext.load_verify_locations` or as a value of the ``ca_certs`` parameter to :func:`wrap_socket`. +.. data:: VERIFY_DEFAULT + + Possible value for :attr:`SSLContext.verify_flags`. In this mode, + certificate revocation lists (CRLs) are not checked. By default OpenSSL + does neither require nor verify CRLs. + + .. versionadded:: 3.4 + +.. data:: VERIFY_CRL_CHECK_LEAF + + Possible value for :attr:`SSLContext.verify_flags`. In this mode, only the + peer cert is check but non of the intermediate CA certificates. The mode + requires a valid CRL that is signed by the peer cert's issuer (its direct + ancestor CA). If no proper has been loaded + :attr:`SSLContext.load_verify_locations`, validation will fail. + + .. versionadded:: 3.4 + +.. data:: VERIFY_CRL_CHECK_CHAIN + + Possible value for :attr:`SSLContext.verify_flags`. In this mode, CRLs of + all certificates in the peer cert chain are checked. + + .. versionadded:: 3.4 + +.. data:: VERIFY_X509_STRICT + + Possible value for :attr:`SSLContext.verify_flags` to disable workarounds + for broken X.509 certificates. + + .. versionadded:: 3.4 + .. data:: PROTOCOL_SSLv2 Selects SSL version 2 as the channel encryption protocol. @@ -862,6 +894,10 @@ to speed up repeated connections from the same clients. other peers' certificates when :data:`verify_mode` is other than :data:`CERT_NONE`. At least one of *cafile* or *capath* must be specified. + This method can also load certification revocation lists (CRLs) in PEM or + or DER format. In order to make use of CRLs, :attr:`SSLContext.verify_flags` + must be configured properly. + The *cafile* string, if present, is the path to a file of concatenated CA certificates in PEM format. See the discussion of :ref:`ssl-certificates` for more information about how to arrange the @@ -880,6 +916,7 @@ to speed up repeated connections from the same clients. .. versionchanged:: 3.4 New optional argument *cadata* + .. method:: SSLContext.get_ca_certs(binary_form=False) Get a list of loaded "certification authority" (CA) certificates. If the @@ -1057,6 +1094,14 @@ to speed up repeated connections from the same clients. The protocol version chosen when constructing the context. This attribute is read-only. +.. attribute:: SSLContext.verify_flags + + The flags for certificate verification operations. You can set flags like + :data:`VERIFY_CRL_CHECK_LEAF` by ORing them together. By default OpenSSL + does neither require nor verify certificate revocation lists (CRLs). + + .. versionadded:: 3.4 + .. attribute:: SSLContext.verify_mode Whether to try to verify other peers' certificates and how to behave @@ -102,6 +102,8 @@ from _ssl import ( SSLSyscallError, SSLEOFError, ) from _ssl import CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED +from _ssl import (VERIFY_DEFAULT, VERIFY_CRL_CHECK_LEAF, VERIFY_CRL_CHECK_CHAIN, + VERIFY_X509_STRICT) from _ssl import txt2obj as _txt2obj, nid2obj as _nid2obj from _ssl import RAND_status, RAND_egd, RAND_add, RAND_bytes, RAND_pseudo_bytes diff --git a/Lib/test/make_ssl_certs.py b/Lib/test/make_ssl_certs.py index f630813..4251d55 100644 --- a/Lib/test/make_ssl_certs.py +++ b/Lib/test/make_ssl_certs.py @@ -28,8 +28,10 @@ req_template = """ [ CA_default ] dir = cadir database = $dir/index.txt + crlnumber = $dir/crl.txt default_md = sha1 default_days = 3600 + default_crl_days = 3600 certificate = pycacert.pem private_key = pycakey.pem serial = $dir/serial @@ -112,6 +114,8 @@ def make_ca(): os.mkdir(TMP_CADIR) with open(os.path.join('cadir','index.txt'),'a+') as f: pass # empty file + with open(os.path.join('cadir','crl.txt'),'a+') as f: + r.write("00") with open(os.path.join('cadir','index.txt.attr'),'w+') as f: f.write('unique_subject = no') @@ -129,6 +133,8 @@ def make_ca(): '-keyfile', 'pycakey.pem', '-days', '3650', '-selfsign', '-extensions', 'v3_ca', '-infiles', f.name ] check_call(['openssl'] + args) + args = ['ca', '-config', t.name, '-gencrl', '-out', 'revocation.crl'] + check_call(['openssl'] + args) if __name__ == '__main__': os.chdir(here) diff --git a/Lib/test/revocation.crl b/Lib/test/revocation.crl new file mode 100644 index 0000000..6d89b08 --- /dev/null +++ b/Lib/test/revocation.crl @@ -0,0 +1,11 @@ +-----BEGIN X509 CRL----- +MIIBpjCBjwIBATANBgkqhkiG9w0BAQUFADBNMQswCQYDVQQGEwJYWTEmMCQGA1UE +CgwdUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24gQ0ExFjAUBgNVBAMMDW91ci1j +YS1zZXJ2ZXIXDTEzMTEyMTE3MDg0N1oXDTIzMDkzMDE3MDg0N1qgDjAMMAoGA1Ud +FAQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQCNJXC2mVKauEeN3LlQ3ZtM5gkH3ExH ++i4bmJjtJn497WwvvoIeUdrmVXgJQR93RtV37hZwN0SXMLlNmUZPH4rHhihayw4m +unCzVj/OhCCY7/TPjKuJ1O/0XhaLBpBVjQN7R/1ujoRKbSia/CD3vcn7Fqxzw7LK +fSRCKRGTj1CZiuxrphtFchwALXSiFDy9mr2ZKhImcyq1PydfgEzU78APpOkMQsIC +UNJ/cf3c9emzf+dUtcMEcejQ3mynBo4eIGg1EW42bz4q4hSjzQlKcBV0muw5qXhc +HOxH2iTFhQ7SrvVuK/dM14rYM4B5mSX3nRC1kNmXpS9j3wJDhuwmjHed +-----END X509 CRL----- diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 8016728..2da1386 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -48,6 +48,9 @@ CAFILE_NEURONIO = data_file("capath", "4e1295a3.0") CAFILE_CACERT = data_file("capath", "5ed36f99.0") +# empty CRL +CRLFILE = data_file("revocation.crl") + # Two keys and certs signed by the same CA (for SNI tests) SIGNED_CERTFILE = data_file("keycert3.pem") SIGNED_CERTFILE2 = data_file("keycert4.pem") @@ -631,7 +634,7 @@ class ContextTests(unittest.TestCase): with self.assertRaises(ValueError): ctx.options = 0 - def test_verify(self): + def test_verify_mode(self): ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) # Default value self.assertEqual(ctx.verify_mode, ssl.CERT_NONE) @@ -646,6 +649,23 @@ class ContextTests(unittest.TestCase): with self.assertRaises(ValueError): ctx.verify_mode = 42 + def test_verify_flags(self): + ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + # default value by OpenSSL + self.assertEqual(ctx.verify_flags, ssl.VERIFY_DEFAULT) + ctx.verify_flags = ssl.VERIFY_CRL_CHECK_LEAF + self.assertEqual(ctx.verify_flags, ssl.VERIFY_CRL_CHECK_LEAF) + ctx.verify_flags = ssl.VERIFY_CRL_CHECK_CHAIN + self.assertEqual(ctx.verify_flags, ssl.VERIFY_CRL_CHECK_CHAIN) + ctx.verify_flags = ssl.VERIFY_DEFAULT + self.assertEqual(ctx.verify_flags, ssl.VERIFY_DEFAULT) + # supports any value + ctx.verify_flags = ssl.VERIFY_CRL_CHECK_LEAF | ssl.VERIFY_X509_STRICT + self.assertEqual(ctx.verify_flags, + ssl.VERIFY_CRL_CHECK_LEAF | ssl.VERIFY_X509_STRICT) + with self.assertRaises(TypeError): + ctx.verify_flags = None + def test_load_cert_chain(self): ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) # Combined key and cert in a single file @@ -1771,6 +1791,47 @@ else: self.assertLess(before, after) s.close() + def test_crl_check(self): + if support.verbose: + sys.stdout.write("\n") + + server_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + server_context.load_cert_chain(SIGNED_CERTFILE) + + context = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + context.verify_mode = ssl.CERT_REQUIRED + context.load_verify_locations(SIGNING_CA) + context.verify_mode = ssl.CERT_REQUIRED + context.verify_flags = ssl.VERIFY_DEFAULT + + # VERIFY_DEFAULT should pass + server = ThreadedEchoServer(context=server_context, chatty=True) + with server: + with context.wrap_socket(socket.socket()) as s: + s.connect((HOST, server.port)) + cert = s.getpeercert() + self.assertTrue(cert, "Can't get peer certificate.") + + # VERIFY_CRL_CHECK_LEAF without a loaded CRL file fails + context.verify_flags = ssl.VERIFY_CRL_CHECK_LEAF + + server = ThreadedEchoServer(context=server_context, chatty=True) + with server: + with context.wrap_socket(socket.socket()) as s: + with self.assertRaisesRegex(ssl.SSLError, + "certificate verify failed"): + s.connect((HOST, server.port)) + + # now load a CRL file. The CRL file is signed by the CA. + context.load_verify_locations(CRLFILE) + + server = ThreadedEchoServer(context=server_context, chatty=True) + with server: + with context.wrap_socket(socket.socket()) as s: + s.connect((HOST, server.port)) + cert = s.getpeercert() + self.assertTrue(cert, "Can't get peer certificate.") + def test_empty_cert(self): """Connecting with an empty cert file""" bad_cert_test(os.path.join(os.path.dirname(__file__) or os.curdir, @@ -59,6 +59,10 @@ Core and Builtins Library ------- +- Issue #8813: Add SSLContext.verify_flags to change the verification flags + of the context in order to enable certification revocation list (CRL) + checks or strict X509 rules. + - Issue #18294: Fix the zlib module to make it 64-bit safe. - Issue #19682: Fix compatibility issue with old version of OpenSSL that diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 3a72530..634eea5 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -2231,6 +2231,44 @@ set_verify_mode(PySSLContext *self, PyObject *arg, void *c) } static PyObject * +get_verify_flags(PySSLContext *self, void *c) +{ + X509_STORE *store; + unsigned long flags; + + store = SSL_CTX_get_cert_store(self->ctx); + flags = X509_VERIFY_PARAM_get_flags(store->param); + return PyLong_FromUnsignedLong(flags); +} + +static int +set_verify_flags(PySSLContext *self, PyObject *arg, void *c) +{ + X509_STORE *store; + unsigned long new_flags, flags, set, clear; + + if (!PyArg_Parse(arg, "k", &new_flags)) + return -1; + store = SSL_CTX_get_cert_store(self->ctx); + flags = X509_VERIFY_PARAM_get_flags(store->param); + clear = flags & ~new_flags; + set = ~flags & new_flags; + if (clear) { + if (!X509_VERIFY_PARAM_clear_flags(store->param, clear)) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + return -1; + } + } + if (set) { + if (!X509_VERIFY_PARAM_set_flags(store->param, set)) { + _setSSLError(NULL, 0, __FILE__, __LINE__); + return -1; + } + } + return 0; +} + +static PyObject * get_options(PySSLContext *self, void *c) { return PyLong_FromLong(SSL_CTX_get_options(self->ctx)); @@ -3048,6 +3086,8 @@ get_ca_certs(PySSLContext *self, PyObject *args) static PyGetSetDef context_getsetlist[] = { {"options", (getter) get_options, (setter) set_options, NULL}, + {"verify_flags", (getter) get_verify_flags, + (setter) set_verify_flags, NULL}, {"verify_mode", (getter) get_verify_mode, (setter) set_verify_mode, NULL}, {NULL}, /* sentinel */ @@ -3761,6 +3801,15 @@ PyInit__ssl(void) PY_SSL_CERT_OPTIONAL); PyModule_AddIntConstant(m, "CERT_REQUIRED", PY_SSL_CERT_REQUIRED); + /* CRL verification for verification_flags */ + PyModule_AddIntConstant(m, "VERIFY_DEFAULT", + 0); + PyModule_AddIntConstant(m, "VERIFY_CRL_CHECK_LEAF", + X509_V_FLAG_CRL_CHECK); + PyModule_AddIntConstant(m, "VERIFY_CRL_CHECK_CHAIN", + X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL); + PyModule_AddIntConstant(m, "VERIFY_X509_STRICT", + X509_V_FLAG_X509_STRICT); #ifdef _MSC_VER /* Windows dwCertEncodingType */ |