mirror of https://github.com/python/cpython.git
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.
This commit is contained in:
parent
e079eddf21
commit
225877917e
|
@ -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 @@
|
|||
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
|
||||
|
||||
|
|
|
@ -28,8 +28,10 @@
|
|||
[ 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)
|
||||
|
|
|
@ -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-----
|
|
@ -48,6 +48,9 @@ def data_file(*name):
|
|||
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 @@ def test_options(self):
|
|||
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 @@ def test_verify(self):
|
|||
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 @@ def test_getpeercert(self):
|
|||
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
|
||||
|
|
|
@ -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 */
|
||||
|
|
Loading…
Reference in New Issue