gh-134531: refactor _hashlib logic for handling NIDs and EVP_MDs (#135254)

This commit is contained in:
Bénédikt Tran 2025-06-08 14:34:57 +02:00 committed by GitHub
parent 158e5162bf
commit aee45fd03f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -368,41 +368,83 @@ notify_ssl_error_occurred(void)
} }
/* LCOV_EXCL_STOP */ /* LCOV_EXCL_STOP */
static const char * /*
get_openssl_evp_md_utf8name(const EVP_MD *md) * 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
assert(md != NULL); * the underlying digest name (only the block size and digest size are allowed
int nid = EVP_MD_nid(md); * to be recovered).
const char *name = NULL; *
const py_hashentry_t *h; * 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) { if (h->ossl_nid == nid) {
name = h->py_name; return h;
break;
} }
} }
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) { if (name == NULL) {
/* Ignore aliased names and only use long, lowercase name. The aliases // In OpenSSL 3.0 and later, OBJ_nid*() are thread-safe and may raise.
* pollute the list and OpenSSL appears to have its own definition of assert(ERR_peek_last_error() != 0);
* alias as the resulting list still contains duplicate and alternate if (ERR_GET_REASON(ERR_peek_last_error()) != OBJ_R_UNKNOWN_NID) {
* names for several algorithms. notify_ssl_error_occurred();
*/ return NULL;
name = OBJ_nid2ln(nid); }
if (name == NULL) // fallback to short name and unconditionally propagate errors
name = OBJ_nid2sn(nid); name = OBJ_nid2sn(nid);
if (name == NULL) {
raise_ssl_error(PyExc_ValueError, "cannot resolve NID %d", nid);
}
} }
return name; 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 char *name = get_openssl_evp_md_utf8name(md); const py_hashentry_t *e = get_hashentry_by_nid(nid);
return PyUnicode_FromString(name); return e ? e->py_name : get_asn1_utf8name_by_nid(nid);
} }
/* Get EVP_MD by HID and purpose */ /* 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)
{
assert(md != NULL);
return get_hashlib_utf8name_by_nid(EVP_MD_nid(md));
}
/*
* 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 * static PY_EVP_MD *
get_openssl_evp_md_by_utf8name(PyObject *module, const char *name, get_openssl_evp_md_by_utf8name(PyObject *module, const char *name,
Py_hash_type py_ht) Py_hash_type py_ht)
@ -471,42 +513,46 @@ get_openssl_evp_md_by_utf8name(PyObject *module, const char *name,
return digest; 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.
* *
* * string * On error, set an exception and return NULL.
* * _hashopenssl builtin function
* *
* on error returns NULL with exception set. * Parameters
*
* digestmod A digest name or a _hashopenssl builtin function
* py_ht The message digest purpose.
*/ */
static PY_EVP_MD * static PY_EVP_MD *
get_openssl_evp_md(PyObject *module, PyObject *digestmod, get_openssl_evp_md(PyObject *module, PyObject *digestmod, Py_hash_type py_ht)
Py_hash_type py_ht)
{ {
PyObject *name_obj = NULL;
const char *name; const char *name;
if (PyUnicode_Check(digestmod)) { if (PyUnicode_Check(digestmod)) {
name_obj = digestmod; name = PyUnicode_AsUTF8(digestmod);
} else {
_hashlibstate *state = get_hashlib_state(module);
// borrowed ref
name_obj = PyDict_GetItemWithError(state->constructs, digestmod);
} }
if (name_obj == 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);
}
if (name == NULL) {
if (!PyErr_Occurred()) { if (!PyErr_Occurred()) {
_hashlibstate *state = get_hashlib_state(module); raise_unsupported_digestmod_error(module, digestmod);
PyErr_Format(
state->unsupported_digestmod_error,
"Unsupported digestmod %R", digestmod);
} }
return NULL; return NULL;
} }
name = PyUnicode_AsUTF8(name_obj);
if (name == NULL) {
return NULL;
}
return get_openssl_evp_md_by_utf8name(module, name, py_ht); return get_openssl_evp_md_by_utf8name(module, name, py_ht);
} }
@ -745,7 +791,9 @@ _hashlib_HASH_get_name(PyObject *op, void *Py_UNUSED(closure))
notify_ssl_error_occurred(); notify_ssl_error_occurred();
return NULL; 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[] = { static PyGetSetDef HASH_getsets[] = {
@ -1775,20 +1823,15 @@ _hmac_dealloc(PyObject *op)
static PyObject * static PyObject *
_hmac_repr(PyObject *op) _hmac_repr(PyObject *op)
{ {
const char *digest_name;
HMACobject *self = HMACobject_CAST(op); HMACobject *self = HMACobject_CAST(op);
const EVP_MD *md = _hashlib_hmac_get_md(self); const EVP_MD *md = _hashlib_hmac_get_md(self);
if (md == NULL) { digest_name = md == NULL ? NULL : get_hashlib_utf8name_by_evp_md(md);
return NULL;
}
PyObject *digest_name = get_openssl_evp_md_name(md);
if (digest_name == NULL) { if (digest_name == NULL) {
assert(PyErr_Occurred());
return NULL; return NULL;
} }
PyObject *repr = PyUnicode_FromFormat( return PyUnicode_FromFormat("<%s HMAC object @ %p>", digest_name, self);
"<%U HMAC object @ %p>", digest_name, self
);
Py_DECREF(digest_name);
return repr;
} }
/*[clinic input] /*[clinic input]
@ -1900,13 +1943,12 @@ _hashlib_hmac_get_name(PyObject *op, void *Py_UNUSED(closure))
if (md == NULL) { if (md == NULL) {
return 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) { if (digest_name == NULL) {
assert(PyErr_Occurred());
return NULL; return NULL;
} }
PyObject *name = PyUnicode_FromFormat("hmac-%U", digest_name); return PyUnicode_FromFormat("hmac-%s", digest_name);
Py_DECREF(digest_name);
return name;
} }
static PyMethodDef HMAC_methods[] = { static PyMethodDef HMAC_methods[] = {
@ -1982,7 +2024,9 @@ _openssl_hash_name_mapper(const EVP_MD *md, const char *from,
return; 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) { if (py_name == NULL) {
state->error = 1; state->error = 1;
} else { } else {