]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Issue #8813: Add SSLContext.verify_flags to change the verification flags
authorChristian Heimes <christian@cheimes.de>
Thu, 21 Nov 2013 22:56:13 +0000 (23:56 +0100)
committerChristian Heimes <christian@cheimes.de>
Thu, 21 Nov 2013 22:56:13 +0000 (23:56 +0100)
of the context in order to enable certification revocation list (CRL)
checks or strict X509 rules.

Doc/library/ssl.rst
Lib/ssl.py
Lib/test/make_ssl_certs.py
Lib/test/revocation.crl [new file with mode: 0644]
Lib/test/test_ssl.py
Misc/NEWS
Modules/_ssl.c

index ce37c95c4089a9f701a1762d843ccffcdec5f9e1..99386c0132df18b2b5648f1164ab6a227f48edee 100644 (file)
@@ -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
index 91956c6a115e50187f75fb2425fb465717f3f912..7ce097f041711312840af22f8e0cc3531c0e5d85 100644 (file)
@@ -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
 
index f630813b2c23573b25211297b06abf70f930c436..4251d5524ffa39b9094b2e4d0cbc6ca9b5734702 100644 (file)
@@ -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 (file)
index 0000000..6d89b08
--- /dev/null
@@ -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-----
index 8016728136e39a94caf7c2eb9b37b0df22590075..2da1386992b34aef9747344a5644705015ecf4ab 100644 (file)
@@ -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,
index 45602f7c66efa79f6d2dfd95c14cd1a371aaa46d..f1f0680ad9bcdb3f9a862a31d9c03b74e0f78db3 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -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
index 3a72530c37019b6c3abf4aa020e182f99daff15f..634eea568cf7a52822c1da087d8562ff80f9d339 100644 (file)
@@ -2230,6 +2230,44 @@ set_verify_mode(PySSLContext *self, PyObject *arg, void *c)
     return 0;
 }
 
+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)
 {
@@ -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 */