GeezerVison is showing github.com — SSLContext.load_verify_locations(cadata=...) does not accept CRLs (unlike cafile and capath) · Issue #149679 · python/cpython
SSLContext.load_verify_locations(cadata=...) does not accept CRLs (unlike cafile and capath) #149679DescriptionFeature or enhancement
Proposal:
SSLContext.load_verify_locations can load CA certificates or CRLs to validate against. It provides three parameters: cafile, capath and cadata. While all three can be used to load certificates, only the first two can load revocation lists.
If I have the CRL already in memory (e.g. because some earlier code downloaded and inspected the CRL before use), it has to be written to a file in order to load it.
The following code does not work - it raises an exception:
crl = ... # DER-encoded CRL loaded previously
ctx = ssl.create_default_context()
ctx.load_verify_locations(cadata=crl)
Traceback (most recent call last):
File "<python-input-3>", line 1, in <module>
ctx.load_verify_locations(cadata=crl)
~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
ssl.SSLError: not enough data: cadata does not contain a certificate (_ssl.c:4219)
This workaround works, but is a bit ugly:
crl = ...
ctx = ssl.create_default_context()
with tempfile.NamedTemporaryFile(buffering=0) as f:
f.write(crl)
f.flush()
ctx.load_verify_locations(cafile=f.name)
Also, the documentation is not clear on this, currently: Above the description of the three parameters, it says, "This method can also load certification revocation lists (CRLs) in PEM or DER format". In the description of each parameter, only certificates (not CRLs) are mentioned, e.g. "The cafile string, if present, is the path to a file of concatenated CA certificates in PEM format".
There is also no indication that only cafile and capath can take CRLs, and that using cadata for this fails.
Has this already been discussed elsewhere?
This is a minor feature, which does not need previous discussion elsewhere
Links to previous discussion of this feature:
No response
Linked PRs
Activitypicnixz commented on May 13, 2026 For cadata:
The cadata object, if present, is either an ASCII string of one or more PEM-encoded certificates or a bytes-like object of DER-encoded certificates.
From your code, I assume that your CRL is a DER-encoded string, not a PEM-encoded string. If you want to supply a DER-encoded certificate, you need a bytes string instead. uedvt359 commented on May 13, 2026
If you want to supply a DER-encoded certificate, you need a bytes string instead.
i have tried both der-encoded csrs as bytestrings, and pem-encoded csrs in both string types - none are accepted. picnixz commented on May 13, 2026 You might want to use ssl.DER_cert_to_PEM_cert to convert them properly. If your DER encoded string is a real string, you should encode() it then decode it to PEM and then give it back (sorry I do not have ways to check it now).
Otherwise, if this does not work, something else is happening and maybe cadata is not meant for CRLs at all (in which case we need to update the docs for cafile/capath to state that they support CRLs).
Alternatively, one can assume that cadata is broken for CRLs and consider it as a bugfix. I need to know whether it was deliberate though so digging into the C code will be needed (I do not know when I will have time) uedvt359 commented on May 13, 2026 this is not an error in my csr handling - the code to load csrs from string/bytes is simly not there.
here is the implementation:
|
if (cadata) { |
|
if (PyUnicode_Check(cadata)) { |
|
PyObject *cadata_ascii = PyUnicode_AsASCIIString(cadata); |
|
if (cadata_ascii == NULL) { |
|
if (PyErr_ExceptionMatches(PyExc_UnicodeEncodeError)) { |
|
goto invalid_cadata; |
|
} |
|
goto error; |
|
} |
|
r = _add_ca_certs(self, |
|
PyBytes_AS_STRING(cadata_ascii), |
|
PyBytes_GET_SIZE(cadata_ascii), |
|
SSL_FILETYPE_PEM); |
|
Py_DECREF(cadata_ascii); |
|
if (r == -1) { |
|
goto error; |
|
} |
|
} |
|
else if (PyObject_CheckBuffer(cadata)) { |
|
Py_buffer buf; |
|
if (PyObject_GetBuffer(cadata, &buf, PyBUF_SIMPLE)) { |
|
goto error; |
|
} |
|
assert(PyBuffer_IsContiguous(&buf, 'C')); |
|
if (buf.ndim > 1) { |
|
PyBuffer_Release(&buf); |
|
PyErr_SetString(PyExc_TypeError, |
|
"cadata should be a contiguous buffer with " |
|
"a single dimension"); |
|
goto error; |
|
} |
|
r = _add_ca_certs(self, buf.buf, buf.len, SSL_FILETYPE_ASN1); |
|
PyBuffer_Release(&buf); |
|
if (r == -1) { |
|
goto error; |
|
} |
|
} |
|
else { |
|
invalid_cadata: |
|
PyErr_SetString(PyExc_TypeError, |
|
"cadata should be an ASCII string or a " |
|
"bytes-like object"); |
|
goto error; |
|
} |
|
} |
cadata is handled seperately from cafile/capath - and cadata is passed to _add_ca_certs(), which does not accept CSRs.
picnixz commented on May 13, 2026 Ah I see, thanks. Mmh, I think the best is to add yet another parameter to that or an alternative function. I would rather see it as an alternative function which takes crldata/crlfile/crlpath instead but I need to think about it more picnixz commented on May 13, 2026 Note: we still need to update the docs though. At least cafile/capath work well with CRL only but cadata does not. uedvt359 commented on May 13, 2026 Python's load_verify_locations seems to be modeled after OpenSSL's SSL_CTX_load_verify_locations (obviously).
The OpenSSL version does not support loading from a buffer, only from file/path, so cadata was a Python extension to that. Loading CRLs with cafile/capath is a feature inherited from the underlying library that has not been implemented in the cadata extension.
Given that, i would prefer all three arguments to work the same (instead of another argument or even function), but i understand when other factors outweigh that. mangrisano commented on Aug 13, 2026 The docs half of this (CRLs load only through cafile/capath, not cadata) is covered by #155701. This is about the feature half: loading a CRL that's already in memory without a temp-file round-trip.
I work with CRLs a fair amount, so a few notes on the two directions raised above.
Extending cadata to also accept CRLs (parity with cafile/capath, @uedvt359's preference). This matches OpenSSL's own SSL_CTX_load_verify_locations, which loads certs and CRLs from the same file/dir; cadata was only ever the in-memory extension of that, so it restores parity. It's also the smallest change: _add_ca_certs() already iterates PEM/DER items, so it would additionally try d2i_X509_CRL_bio / PEM_read_bio_X509_CRL + X509_STORE_add_crl for items that aren't certificates. The downside is that cadata reads as "CA data" and today's error is literally "cadata does not contain a certificate"; accepting CRLs there is implicit and makes a precise error message harder when the blob is neither.
A dedicated crl* API (your lean). A CRL is revocation data, not a trust anchor, so keeping it separate reads better and lets us raise a precise "does not contain a CRL" error. On shape, a new method is cleaner than adding crlfile/crlpath/crldata to load_verify_locations, which already juggles three interdependent cert inputs — e.g. SSLContext.load_crl(crlfile=None, crlpath=None, crldata=None). The fiddly part is crlpath: a directory of CRLs relies on OpenSSL's hashed-dir lookup, so it would mirror capath's lazy behaviour rather than eagerly loading, whereas crlfile/crldata are the straightforward eager cases (PEM_read_bio_X509_CRL / d2i_X509_CRL_bio + X509_STORE_add_crl).
My preference is the dedicated method: explicit, clean diagnostics, and it keeps CA trust and revocation data conceptually separate — which matters when reasoning about verify_flags. But if minimising API surface and OpenSSL parity weigh more, extending cadata is the smaller change and I'm happy either way.
If you can pick a direction, I'll put up a PR with tests.
MetadataMetadataDevelopmentNo branches or pull requestsIssue actions
|