Skip to content

gh-135234: improve _hashlib exceptions when reporting an OpenSSL function failure #135250

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Jun 9, 2025
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor logic for mapping NIDs to EVP_MD objects
  • Loading branch information
picnixz committed Jun 8, 2025
commit f8c2aed03f46fd460230ec51830aed690efdfcf6
169 changes: 107 additions & 62 deletions Modules/_hashopenssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -368,41 +368,83 @@ notify_ssl_error_occurred(void)
}
/* LCOV_EXCL_STOP */

static const char *
get_openssl_evp_md_utf8name(const EVP_MD *md)
{
assert(md != NULL);
int nid = EVP_MD_nid(md);
const char *name = NULL;
const py_hashentry_t *h;
/*
* OpenSSL provides a way to go from NIDs to digest names for hash functions
* but lacks this granularity for MAC objects where it is not possible to get
* the underlying digest name (only the block size and digest size are allowed
* to be recovered).
*
* In addition, OpenSSL aliases pollute the list of known digest names
* as OpenSSL appears to have its own definition of alias. In particular,
* the resulting list still contains duplicate and alternate names for several
* algorithms.
*
* Therefore, digest names, whether they are used by hash functions or HMAC,
* are handled through EVP_MD objects or directly by using some NID.
*/

for (h = py_hashes; h->py_name != NULL; h++) {
/* Get a cached entry by OpenSSL NID. */
static const py_hashentry_t *
get_hashentry_by_nid(int nid)
{
for (const py_hashentry_t *h = py_hashes; h->py_name != NULL; h++) {
if (h->ossl_nid == nid) {
name = h->py_name;
break;
return h;
}
}
return NULL;
}

/*
* Convert the NID to a string via OBJ_nid2_*() functions.
*
* If 'nid' cannot be resolved, set an exception and return NULL.
*/
static const char *
get_asn1_utf8name_by_nid(int nid)
{
const char *name = OBJ_nid2ln(nid);
if (name == NULL) {
/* Ignore aliased names and only use long, lowercase name. The aliases
* pollute the list and OpenSSL appears to have its own definition of
* alias as the resulting list still contains duplicate and alternate
* names for several algorithms.
*/
name = OBJ_nid2ln(nid);
if (name == NULL)
name = OBJ_nid2sn(nid);
// In OpenSSL 3.0 and later, OBJ_nid*() are thread-safe and may raise.
assert(ERR_peek_last_error() != 0);
if (ERR_GET_REASON(ERR_peek_last_error()) != OBJ_R_UNKNOWN_NID) {
notify_ssl_error_occurred();
return NULL;
}
// fallback to short name and unconditionally propagate errors
name = OBJ_nid2sn(nid);
if (name == NULL) {
raise_ssl_error(PyExc_ValueError, "cannot resolve NID %d", nid);
}
}
return name;
}

static PyObject *
get_openssl_evp_md_name(const EVP_MD *md)
/*
* Convert the NID to an OpenSSL digest name.
*
* On error, set an exception and return NULL.
*/
static const char *
get_hashlib_utf8name_by_nid(int nid)
{
const py_hashentry_t *e = get_hashentry_by_nid(nid);
return e ? e->py_name : get_asn1_utf8name_by_nid(nid);
}

/* Same as get_hashlib_utf8name_by_nid() but using an EVP_MD object. */
static const char *
get_hashlib_utf8name_by_evp_md(const EVP_MD *md)
{
const char *name = get_openssl_evp_md_utf8name(md);
return PyUnicode_FromString(name);
assert(md != NULL);
return get_hashlib_utf8name_by_nid(EVP_MD_nid(md));
}

/* Get EVP_MD by HID and purpose */
/*
* Get a new reference to an EVP_MD object described by name and purpose.
*
* If 'name' is an OpenSSL indexed name, the return value is cached.
*/
static PY_EVP_MD *
get_openssl_evp_md_by_utf8name(PyObject *module, const char *name,
Py_hash_type py_ht)
Expand Down Expand Up @@ -464,49 +506,54 @@ get_openssl_evp_md_by_utf8name(PyObject *module, const char *name,
}
}
if (digest == NULL) {
// NOTE(picnixz): report hash type value instead of name
raise_ssl_error(state->unsupported_digestmod_error,
"unsupported hash type %s", name);
return NULL;
}
return digest;
}

/* Get digest EVP_MD from object
/*
* Raise an exception indicating that 'digestmod' is not supported.
*/
static void
raise_unsupported_digestmod_error(PyObject *module, PyObject *digestmod)
{
_hashlibstate *state = get_hashlib_state(module);
PyErr_Format(state->unsupported_digestmod_error,
"Unsupported digestmod %R", digestmod);
}

/*
* Get a new reference to an EVP_MD described by 'digestmod' and purpose.
*
* On error, set an exception and return NULL.
*
* * string
* * _hashopenssl builtin function
* Parameters
*
* on error returns NULL with exception set.
* digestmod A digest name or a _hashlib.openssl_* function
* py_ht The message digest purpose.
*/
static PY_EVP_MD *
get_openssl_evp_md(PyObject *module, PyObject *digestmod,
Py_hash_type py_ht)
get_openssl_evp_md(PyObject *module, PyObject *digestmod, Py_hash_type py_ht)
{
PyObject *name_obj = NULL;
const char *name;

if (PyUnicode_Check(digestmod)) {
name_obj = digestmod;
} else {
_hashlibstate *state = get_hashlib_state(module);
// borrowed ref
name_obj = PyDict_GetItemWithError(state->constructs, digestmod);
name = PyUnicode_AsUTF8(digestmod);
}
if (name_obj == NULL) {
if (!PyErr_Occurred()) {
_hashlibstate *state = get_hashlib_state(module);
PyErr_Format(
state->unsupported_digestmod_error,
"Unsupported digestmod %R", digestmod);
}
return NULL;
else {
PyObject *dict = get_hashlib_state(module)->constructs;
assert(dict != NULL);
PyObject *borrowed_ref = PyDict_GetItemWithError(dict, digestmod);
name = borrowed_ref == NULL ? NULL : PyUnicode_AsUTF8(borrowed_ref);
}

name = PyUnicode_AsUTF8(name_obj);
if (name == NULL) {
if (!PyErr_Occurred()) {
raise_unsupported_digestmod_error(module, digestmod);
}
return NULL;
}

return get_openssl_evp_md_by_utf8name(module, name, py_ht);
}

Expand Down Expand Up @@ -745,7 +792,9 @@ _hashlib_HASH_get_name(PyObject *op, void *Py_UNUSED(closure))
notify_ssl_error_occurred();
return NULL;
}
return get_openssl_evp_md_name(md);
const char *name = get_hashlib_utf8name_by_evp_md(md);
assert(name != NULL || PyErr_Occurred());
return name == NULL ? NULL : PyUnicode_FromString(name);
}

static PyGetSetDef HASH_getsets[] = {
Expand Down Expand Up @@ -1775,20 +1824,15 @@ _hmac_dealloc(PyObject *op)
static PyObject *
_hmac_repr(PyObject *op)
{
const char *digest_name;
HMACobject *self = HMACobject_CAST(op);
const EVP_MD *md = _hashlib_hmac_get_md(self);
if (md == NULL) {
return NULL;
}
PyObject *digest_name = get_openssl_evp_md_name(md);
digest_name = md == NULL ? NULL : get_hashlib_utf8name_by_evp_md(md);
if (digest_name == NULL) {
assert(PyErr_Occurred());
return NULL;
}
PyObject *repr = PyUnicode_FromFormat(
"<%U HMAC object @ %p>", digest_name, self
);
Py_DECREF(digest_name);
return repr;
return PyUnicode_FromFormat("<%s HMAC object @ %p>", digest_name, self);
}

/*[clinic input]
Expand Down Expand Up @@ -1900,13 +1944,12 @@ _hashlib_hmac_get_name(PyObject *op, void *Py_UNUSED(closure))
if (md == NULL) {
return NULL;
}
PyObject *digest_name = get_openssl_evp_md_name(md);
const char *digest_name = get_hashlib_utf8name_by_evp_md(md);
if (digest_name == NULL) {
assert(PyErr_Occurred());
return NULL;
}
PyObject *name = PyUnicode_FromFormat("hmac-%U", digest_name);
Py_DECREF(digest_name);
return name;
return PyUnicode_FromFormat("hmac-%s", digest_name);
}

static PyMethodDef HMAC_methods[] = {
Expand Down Expand Up @@ -1982,7 +2025,9 @@ _openssl_hash_name_mapper(const EVP_MD *md, const char *from,
return;
}

py_name = get_openssl_evp_md_name(md);
const char *name = get_hashlib_utf8name_by_evp_md(md);
assert(name != NULL || PyErr_Occurred());
py_name = name == NULL ? NULL : PyUnicode_FromString(name);
if (py_name == NULL) {
state->error = 1;
} else {
Expand Down
Loading