From c444180099662befc126919fda13061d2f912e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 7 Jun 2025 15:29:43 +0200 Subject: [PATCH 01/32] allow to use EVP_MAC API --- Modules/_hashopenssl.c | 794 +++++++++++++++++++++++++++++------------ 1 file changed, 563 insertions(+), 231 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 331275076d7937..8ce4312b2417e7 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -24,24 +24,31 @@ #include "Python.h" #include "pycore_hashtable.h" -#include "pycore_strhex.h" // _Py_strhex() -#include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_LOAD_PTR_RELAXED +#include "pycore_strhex.h" // _Py_strhex() +#include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_LOAD_PTR_RELAXED #include "hashlib.h" +#include +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +# define Py_HAS_OPENSSL3_SUPPORT +#endif + /* EVP is the preferred interface to hashing in OpenSSL */ #include -#include -#include // FIPS_mode() +#include // FIPS_mode() /* We use the object interface to discover what hashes OpenSSL supports. */ #include #include -#include - -#if OPENSSL_VERSION_NUMBER >= 0x30000000L -# define Py_HAS_OPENSSL3_SUPPORT +#ifdef Py_HAS_OPENSSL3_SUPPORT +# include // OSSL_*_PARAM_* +# include // OSSL_PARAM_construct_*() +#else +# include // HMAC() #endif +#include + #ifndef OPENSSL_THREADS # error "OPENSSL_THREADS is not defined, Python requires thread-safe OpenSSL" #endif @@ -64,11 +71,23 @@ #define PY_EVP_MD_fetch(algorithm, properties) EVP_MD_fetch(NULL, algorithm, properties) #define PY_EVP_MD_up_ref(md) EVP_MD_up_ref(md) #define PY_EVP_MD_free(md) EVP_MD_free(md) + +#define EVP_MAC_INCREF(MAC) (void)EVP_MAC_up_ref(MAC) +#define EVP_MAC_DECREF(MAC) EVP_MAC_free(MAC) +#define Py_HMAC_CTX_TYPE EVP_MAC_CTX #else #define PY_EVP_MD const EVP_MD #define PY_EVP_MD_fetch(algorithm, properties) EVP_get_digestbyname(algorithm) #define PY_EVP_MD_up_ref(md) do {} while(0) #define PY_EVP_MD_free(md) do {} while(0) + +#define EVP_MAC_INCREF(MAC) do {} while (0) +#define EVP_MAC_DECREF(MAC) do {} while (0) +#define Py_HMAC_CTX_TYPE HMAC_CTX +#endif + +#ifdef Py_HAS_OPENSSL3_SUPPORT +#else #endif /* hash alias map and fast lookup @@ -267,6 +286,9 @@ typedef struct { PyObject *constructs; PyObject *unsupported_digestmod_error; _Py_hashtable_t *hashtable; +#ifdef Py_HAS_OPENSSL3_SUPPORT + EVP_MAC *evp_hmac; +#endif } _hashlibstate; static inline _hashlibstate* @@ -289,10 +311,15 @@ typedef struct { typedef struct { PyObject_HEAD - HMAC_CTX *ctx; /* OpenSSL hmac context */ +#ifdef Py_HAS_OPENSSL3_SUPPORT + EVP_MAC_CTX *ctx; /* OpenSSL HMAC EVP-based context */ + int evp_md_nid; /* needed to find the message digest name */ +#else + HMAC_CTX *ctx; /* OpenSSL HMAC plain context */ +#endif // Prevents undefined behavior via multiple threads entering the C API. bool use_mutex; - PyMutex mutex; /* HMAC context lock */ + PyMutex mutex; /* HMAC context lock */ } HMACobject; #define HMACobject_CAST(op) ((HMACobject *)(op)) @@ -311,7 +338,7 @@ class _hashlib.HMAC "HMACobject *" "((_hashlibstate *)PyModule_GetState(module)) /* Set an exception of given type using the given OpenSSL error code. */ static void -set_ssl_exception_from_errcode(PyObject *exc, unsigned long errcode) +set_ssl_exception_from_errcode(PyObject *exc_type, unsigned long errcode) { assert(errcode != 0); @@ -321,13 +348,24 @@ set_ssl_exception_from_errcode(PyObject *exc, unsigned long errcode) const char *reason = ERR_reason_error_string(errcode); if (lib && func) { - PyErr_Format(exc, "[%s: %s] %s", lib, func, reason); + PyErr_Format(exc_type, "[%s: %s] %s", lib, func, reason); } else if (lib) { - PyErr_Format(exc, "[%s] %s", lib, reason); + PyErr_Format(exc_type, "[%s] %s", lib, reason); } else { - PyErr_SetString(exc, reason); + PyErr_SetString(exc_type, reason); + } +} + +static PyObject * +get_smart_ssl_exception_type(unsigned long errcode, PyObject *default_exc_type) +{ + switch (ERR_GET_REASON(errcode)) { + case ERR_R_MALLOC_FAILURE: + return PyExc_MemoryError; + default: + return default_exc_type; } } @@ -339,24 +377,49 @@ set_ssl_exception_from_errcode(PyObject *exc, unsigned long errcode) * to create a C-style formatted fallback message. */ static void -raise_ssl_error(PyObject *exc, const char *fallback_format, ...) +raise_ssl_error(PyObject *exc_type, const char *fallback_format, ...) { assert(fallback_format != NULL); unsigned long errcode = ERR_peek_last_error(); if (errcode) { ERR_clear_error(); - set_ssl_exception_from_errcode(exc, errcode); + set_ssl_exception_from_errcode(exc_type, errcode); } else { va_list vargs; va_start(vargs, fallback_format); - PyErr_FormatV(exc, fallback_format, vargs); + PyErr_FormatV(exc_type, fallback_format, vargs); va_end(vargs); } } /* - * Set an exception with a generic default message after an error occurred. + * Same as raise_ssl_error() but raise a MemoryError + * if the last error reason is ERR_R_MALLOC_FAILURE. + */ +static void +raise_smart_ssl_error(PyObject *exc_type, const char *fallback_format, ...) +{ + + assert(fallback_format != NULL); + unsigned long errcode = ERR_peek_last_error(); + if (errcode) { + ERR_clear_error(); + set_ssl_exception_from_errcode( + get_smart_ssl_exception_type(errcode, exc_type), + errcode + ); + } + else { + va_list vargs; + va_start(vargs, fallback_format); + PyErr_FormatV(exc_type, fallback_format, vargs); + va_end(vargs); + } +} + +/* + * Raise an exception with a generic default message after an error occurred. * * It can also be used without previous calls to SSL built-in functions, * in which case a generic error message is provided. @@ -366,43 +429,108 @@ notify_ssl_error_occurred(void) { raise_ssl_error(PyExc_ValueError, "no reason supplied"); } -/* LCOV_EXCL_STOP */ -static const char * -get_openssl_evp_md_utf8name(const EVP_MD *md) +/* + * Same as notify_ssl_error_occurred() but raise a MemoryError + * if the last error reason is ERR_R_MALLOC_FAILURE. + */ +static inline void +notify_smart_ssl_error_occurred(void) { - assert(md != NULL); - int nid = EVP_MD_nid(md); - const char *name = NULL; - const py_hashentry_t *h; + raise_smart_ssl_error(PyExc_ValueError, "no reason supplied"); +} +/* LCOV_EXCL_STOP */ - for (h = py_hashes; h->py_name != NULL; 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. + */ + +/* 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 or failed, 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. + 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 char *name = get_openssl_evp_md_utf8name(md); - return PyUnicode_FromString(name); + const py_hashentry_t *e = get_hashentry_by_nid(nid); + return e ? e->py_name : get_asn1_utf8name_by_nid(nid); } -/* Get EVP_MD by HID and purpose */ +#ifdef Py_HAS_OPENSSL3_SUPPORT +/* + * Convert the NID to an OpenSSL "canonical" cached, SN_* or LN_* digest name. + * + * On error, set an exception and return NULL. + */ +static const char * +get_openssl_utf8name_by_nid(int nid) +{ + const py_hashentry_t *e = get_hashentry_by_nid(nid); + return e ? e->ossl_name : get_asn1_utf8name_by_nid(nid); +} +#endif + +/* 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 * get_openssl_evp_md_by_utf8name(PyObject *module, const char *name, Py_hash_type py_ht) @@ -464,6 +592,7 @@ 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; @@ -471,44 +600,81 @@ get_openssl_evp_md_by_utf8name(PyObject *module, const char *name, 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 - * * _hashopenssl builtin function + * On error, set an exception and return NULL. * - * on error returns NULL with exception set. + * Parameters + * + * 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) { + 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()) { - _hashlibstate *state = get_hashlib_state(module); - PyErr_Format( - state->unsupported_digestmod_error, - "Unsupported digestmod %R", digestmod); + raise_unsupported_digestmod_error(module, digestmod); } return NULL; } + return get_openssl_evp_md_by_utf8name(module, name, py_ht); +} - name = PyUnicode_AsUTF8(name_obj); - if (name == NULL) { +/* + * Get the "canonical" name of an EVP_MD described by 'digestmod' and purpose. + * + * On error, set an exception and return NULL. + * + * This function should not be used to construct the exposed Python name, + * but rather to invoke OpenSSL EVP_* functions. + */ +#ifdef Py_HAS_OPENSSL3_SUPPORT +static const char * +get_openssl_digest_name(PyObject *module, PyObject *digestmod, + Py_hash_type py_ht, int *evp_md_nid) +{ + if (evp_md_nid != NULL) { + *evp_md_nid = NID_undef; + } + PY_EVP_MD *md = get_openssl_evp_md(module, digestmod, py_ht); + if (md == NULL) { return NULL; } - - return get_openssl_evp_md_by_utf8name(module, name, py_ht); + int nid = EVP_MD_nid(md); + if (evp_md_nid != NULL) { + *evp_md_nid = nid; + } + const char *name = get_openssl_utf8name_by_nid(nid); + PY_EVP_MD_free(md); + if (name == NULL) { + raise_unsupported_digestmod_error(module, digestmod); + } + return name; } +#endif static HASHobject * new_hash_object(PyTypeObject *type) @@ -745,7 +911,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[] = { @@ -1520,9 +1688,27 @@ _hashlib_scrypt_impl(PyObject *module, Py_buffer *password, Py_buffer *salt, } #endif /* PY_OPENSSL_HAS_SCRYPT */ -/* Fast HMAC for hmac.digest() +// --- OpenSSL HMAC interface ------------------------------------------------- + +/* + * Functions prefixed by hashlib_openssl_HMAC_* are wrappers around OpenSSL + * and implement "atomic" operations (e.g., "free"). These functions are used + * by those prefixed by _hashlib_HMAC_* that are methods for HMAC objects, or + * other (local) helper functions prefixed by hashlib_HMAC_*. */ +#ifdef Py_HAS_OPENSSL3_SUPPORT +/* EVP_MAC_CTX array of parameters specifying the "digest" */ +#define HASHLIB_HMAC_PARAMS(DIGEST) \ + (const OSSL_PARAM []) { \ + OSSL_PARAM_utf8_string(OSSL_MAC_PARAM_DIGEST, \ + (char *)DIGEST, strlen(DIGEST)), \ + OSSL_PARAM_END \ + } +#endif + +// --- One-shot HMAC interface ------------------------------------------------ + /*[clinic input] _hashlib.hmac_digest as _hashlib_hmac_singleshot @@ -1539,9 +1725,17 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, /*[clinic end generated code: output=82f19965d12706ac input=0a0790cc3db45c2e]*/ { unsigned char md[EVP_MAX_MD_SIZE] = {0}; +#ifdef Py_HAS_OPENSSL3_SUPPORT + size_t md_len = 0; +#else unsigned int md_len = 0; +#endif unsigned char *result; +#ifdef Py_HAS_OPENSSL3_SUPPORT + const char *digest_name = NULL; +#else PY_EVP_MD *evp; +#endif if (key->len > INT_MAX) { PyErr_SetString(PyExc_OverflowError, @@ -1554,6 +1748,22 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, return NULL; } +#ifdef Py_HAS_OPENSSL3_SUPPORT + digest_name = get_openssl_digest_name(module, digest, Py_ht_mac, NULL); + if (digest_name == NULL) { + return NULL; + } + Py_BEGIN_ALLOW_THREADS + result = EVP_Q_mac( + NULL, OSSL_MAC_NAME_HMAC, NULL, NULL, + HASHLIB_HMAC_PARAMS(digest_name), + (const void *)key->buf, (size_t)key->len, + (const unsigned char *)msg->buf, (size_t)msg->len, + md, sizeof(md), &md_len + ); + Py_END_ALLOW_THREADS + assert(md_len < (size_t)PY_SSIZE_T_MAX); +#else evp = get_openssl_evp_md(module, digest, Py_ht_mac); if (evp == NULL) { return NULL; @@ -1568,6 +1778,7 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, ); Py_END_ALLOW_THREADS PY_EVP_MD_free(evp); +#endif if (result == NULL) { notify_ssl_error_occurred(); @@ -1576,20 +1787,109 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, return PyBytes_FromStringAndSize((const char*)md, md_len); } -/* OpenSSL-based HMAC implementation - */ - -static int _hmac_update(HMACobject*, PyObject*); +// --- HMAC Object ------------------------------------------------------------ +#ifndef Py_HAS_OPENSSL3_SUPPORT static const EVP_MD * -_hashlib_hmac_get_md(HMACobject *self) +hashlib_openssl_HMAC_evp_md_borrowed(HMACobject *self) { + assert(self->ctx != NULL); const EVP_MD *md = HMAC_CTX_get_md(self->ctx); if (md == NULL) { raise_ssl_error(PyExc_ValueError, "missing EVP_MD for HMAC context"); } return md; } +#endif + +#ifdef Py_HAS_OPENSSL3_SUPPORT +#define HASHLIB_OPENSSL_HMAC_UPDATE_ONCE EVP_MAC_update +#else +#define HASHLIB_OPENSSL_HMAC_UPDATE_ONCE HMAC_Update +#endif + +static void +hashlib_openssl_HMAC_ctx_free(Py_HMAC_CTX_TYPE *ctx) +{ + /* The NULL check was not present in every OpenSSL versions. */ + if (ctx) { +#ifdef Py_HAS_OPENSSL3_SUPPORT + EVP_MAC_CTX_free(ctx); +#else + HMAC_CTX_free(ctx); +#endif + } +} + +#define hashlib_openssl_HMAC_ctx_clear(CTX) \ + do { \ + hashlib_openssl_HMAC_ctx_free(CTX); \ + CTX = NULL; \ + } while (0) + +static int +hashlib_openssl_HMAC_update_with_lock(HMACobject *self, PyObject *data) +{ + int r; + Py_buffer view = {0}; + GET_BUFFER_VIEW_OR_ERROR(data, &view, return -1); + if (!self->use_mutex && view.len >= HASHLIB_GIL_MINSIZE) { + // TODO(picnixz): disable mutex afterwards + self->use_mutex = true; + } + if (self->use_mutex) { + Py_BEGIN_ALLOW_THREADS + PyMutex_Lock(&self->mutex); + r = HASHLIB_OPENSSL_HMAC_UPDATE_ONCE(self->ctx, + (const unsigned char *)view.buf, + (size_t)view.len); + PyMutex_Unlock(&self->mutex); + Py_END_ALLOW_THREADS + } + else { + r = HASHLIB_OPENSSL_HMAC_UPDATE_ONCE(self->ctx, + (const unsigned char *)view.buf, + (size_t)view.len); + } + PyBuffer_Release(&view); + if (r == 0) { + notify_ssl_error_occurred(); + return -1; + } + return 0; +} + +static Py_HMAC_CTX_TYPE * +hashlib_openssl_HMAC_ctx_copy_with_lock(HMACobject *self) +{ + Py_HMAC_CTX_TYPE *ctx = NULL; +#ifdef Py_HAS_OPENSSL3_SUPPORT + ENTER_HASHLIB(self); + ctx = EVP_MAC_CTX_dup(self->ctx); + LEAVE_HASHLIB(self); + if (ctx == NULL) { + goto error; + } +#else + int r; + ctx = HMAC_CTX_new(); + if (ctx == NULL) { + goto error; + } + ENTER_HASHLIB(self); + r = HMAC_CTX_copy(ctx, self->ctx); + LEAVE_HASHLIB(self); + if (r == 0) { + goto error; + } +#endif + return ctx; + +error: + hashlib_openssl_HMAC_ctx_free(ctx); + notify_smart_ssl_error_occurred(); + return NULL; +} /*[clinic input] _hashlib.hmac_new @@ -1606,9 +1906,12 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj, PyObject *digestmod) /*[clinic end generated code: output=c20d9e4d9ed6d219 input=5f4071dcc7f34362]*/ { - PY_EVP_MD *digest; - HMAC_CTX *ctx = NULL; + _hashlibstate *state = get_hashlib_state(module); HMACobject *self = NULL; + Py_HMAC_CTX_TYPE *ctx = NULL; +#ifdef Py_HAS_OPENSSL3_SUPPORT + int evp_md_nid = NID_undef; +#endif int r; if (key->len > INT_MAX) { @@ -1623,26 +1926,65 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj, return NULL; } - digest = get_openssl_evp_md(module, digestmod, Py_ht_mac); +#ifdef Py_HAS_OPENSSL3_SUPPORT + /* + * OpenSSL 3.0 does not provide a way to extract the NID from an EVP_MAC + * object and does not expose the underlying digest name. The reason is + * that OpenSSL 3.0 treats HMAC objects as being the "same", differing + * only by their *context* parameters. While it is *required* to set + * the digest name when constructing EVP_MAC_CTX objects, that name + * is unfortunately not recoverable through EVP_MAC_CTX_get_params(). + * + * On the other hand, the (deprecated) interface based on HMAC_CTX is + * based on EVP_MD, which allows to treat HMAC objects as if they were + * hash functions when querying the digest name. + * + * Since HMAC objects are constructed from DIGESTMOD values and since + * we have a way to map DIGESTMOD to EVP_MD objects, and then to NIDs, + * HMAC objects based on EVP_MAC will store the NID of the EVP_MD we + * used to deduce the digest name to pass to EVP_MAC_CTX_set_params(). + */ + const char *digest = get_openssl_digest_name( + module, digestmod, Py_ht_mac, &evp_md_nid + ); if (digest == NULL) { return NULL; } + assert(evp_md_nid != NID_undef); + EVP_MAC_up_ref(state->evp_hmac); + ctx = EVP_MAC_CTX_new(state->evp_hmac); + if (ctx == NULL) { + EVP_MAC_free(state->evp_hmac); + notify_smart_ssl_error_occurred(); + return NULL; + } + r = EVP_MAC_init( + ctx, + (const unsigned char *)key->buf, + (size_t)key->len, + HASHLIB_HMAC_PARAMS(digest) + ); +#else + PY_EVP_MD *digest = get_openssl_evp_md(module, digestmod, Py_ht_mac); + if (digest == NULL) { + return NULL; + } ctx = HMAC_CTX_new(); if (ctx == NULL) { PY_EVP_MD_free(digest); - PyErr_NoMemory(); + notify_smart_ssl_error_occurred(); goto error; } r = HMAC_Init_ex(ctx, key->buf, (int)key->len, digest, NULL /* impl */); PY_EVP_MD_free(digest); +#endif if (r == 0) { notify_ssl_error_occurred(); goto error; } - _hashlibstate *state = get_hashlib_state(module); self = PyObject_New(HMACobject, state->HMACtype); if (self == NULL) { goto error; @@ -1650,82 +1992,26 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj, self->ctx = ctx; ctx = NULL; // 'ctx' is now owned by 'self' +#ifdef Py_HAS_OPENSSL3_SUPPORT + assert(evp_md_nid != NID_undef); + self->evp_md_nid = evp_md_nid; +#endif HASHLIB_INIT_MUTEX(self); + /* feed initial data */ if ((msg_obj != NULL) && (msg_obj != Py_None)) { - if (!_hmac_update(self, msg_obj)) { + if (hashlib_openssl_HMAC_update_with_lock(self, msg_obj) < 0) { goto error; } } return (PyObject *)self; error: - if (ctx) HMAC_CTX_free(ctx); + hashlib_openssl_HMAC_ctx_free(ctx); Py_XDECREF(self); return NULL; } -/* helper functions */ -static int -locked_HMAC_CTX_copy(HMAC_CTX *new_ctx_p, HMACobject *self) -{ - int result; - ENTER_HASHLIB(self); - result = HMAC_CTX_copy(new_ctx_p, self->ctx); - LEAVE_HASHLIB(self); - return result; -} - -/* returning 0 means that an error occurred and an exception is set */ -static unsigned int -_hashlib_hmac_digest_size(HMACobject *self) -{ - const EVP_MD *md = _hashlib_hmac_get_md(self); - if (md == NULL) { - return 0; - } - unsigned int digest_size = EVP_MD_size(md); - assert(digest_size <= EVP_MAX_MD_SIZE); - if (digest_size == 0) { - raise_ssl_error(PyExc_ValueError, "invalid digest size"); - } - return digest_size; -} - -static int -_hmac_update(HMACobject *self, PyObject *obj) -{ - int r; - Py_buffer view = {0}; - - GET_BUFFER_VIEW_OR_ERROR(obj, &view, return 0); - - if (!self->use_mutex && view.len >= HASHLIB_GIL_MINSIZE) { - self->use_mutex = true; - } - if (self->use_mutex) { - Py_BEGIN_ALLOW_THREADS - PyMutex_Lock(&self->mutex); - r = HMAC_Update(self->ctx, - (const unsigned char *)view.buf, - (size_t)view.len); - PyMutex_Unlock(&self->mutex); - Py_END_ALLOW_THREADS - } else { - r = HMAC_Update(self->ctx, - (const unsigned char *)view.buf, - (size_t)view.len); - } - - PyBuffer_Release(&view); - - if (r == 0) { - notify_ssl_error_occurred(); - return 0; - } - return 1; -} - /*[clinic input] _hashlib.HMAC.copy @@ -1737,58 +2023,46 @@ _hashlib_HMAC_copy_impl(HMACobject *self) /*[clinic end generated code: output=29aa28b452833127 input=e2fa6a05db61a4d6]*/ { HMACobject *retval; - - HMAC_CTX *ctx = HMAC_CTX_new(); + Py_HMAC_CTX_TYPE *ctx = hashlib_openssl_HMAC_ctx_copy_with_lock(self); if (ctx == NULL) { - return PyErr_NoMemory(); - } - if (!locked_HMAC_CTX_copy(ctx, self)) { - HMAC_CTX_free(ctx); - notify_ssl_error_occurred(); return NULL; } - retval = PyObject_New(HMACobject, Py_TYPE(self)); if (retval == NULL) { - HMAC_CTX_free(ctx); + hashlib_openssl_HMAC_ctx_free(ctx); return NULL; } retval->ctx = ctx; HASHLIB_INIT_MUTEX(retval); - return (PyObject *)retval; } static void -_hmac_dealloc(PyObject *op) +_hashlib_HMAC_dealloc(PyObject *op) { HMACobject *self = HMACobject_CAST(op); PyTypeObject *tp = Py_TYPE(self); - if (self->ctx != NULL) { - HMAC_CTX_free(self->ctx); - self->ctx = NULL; - } + hashlib_openssl_HMAC_ctx_clear(self->ctx); PyObject_Free(self); Py_DECREF(tp); } static PyObject * -_hmac_repr(PyObject *op) +_hashlib_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); +#ifdef Py_HAS_OPENSSL3_SUPPORT + digest_name = get_hashlib_utf8name_by_nid(self->evp_md_nid); +#else + const EVP_MD *md = hashlib_openssl_HMAC_evp_md_borrowed(self); + digest_name = md == NULL ? NULL : get_hashlib_utf8name_by_evp_md(md); +#endif 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] @@ -1802,32 +2076,77 @@ static PyObject * _hashlib_HMAC_update_impl(HMACobject *self, PyObject *msg) /*[clinic end generated code: output=f31f0ace8c625b00 input=1829173bb3cfd4e6]*/ { - if (!_hmac_update(self, msg)) { + if (hashlib_openssl_HMAC_update_with_lock(self, msg) < 0) { return NULL; } Py_RETURN_NONE; } -static int -_hmac_digest(HMACobject *self, unsigned char *buf, unsigned int len) +#define BAD_DIGEST_SIZE 0 + +/* + * Return the digest size in bytes. + * + * On error, set an exception and return BAD_DIGEST_SIZE. + */ +static unsigned int +hashlib_openssl_HMAC_digest_size(HMACobject *self) { - HMAC_CTX *temp_ctx = HMAC_CTX_new(); - if (temp_ctx == NULL) { - (void)PyErr_NoMemory(); - return 0; + assert(EVP_MAX_MD_SIZE < INT_MAX); +#ifdef Py_HAS_OPENSSL3_SUPPORT + assert(self->ctx != NULL); + size_t digest_size = EVP_MAC_CTX_get_mac_size(self->ctx); + assert(digest_size <= (size_t)EVP_MAX_MD_SIZE); +#else + const EVP_MD *md = hashlib_openssl_HMAC_evp_md_borrowed(self); + if (md == NULL) { + return BAD_DIGEST_SIZE; } - if (!locked_HMAC_CTX_copy(temp_ctx, self)) { - HMAC_CTX_free(temp_ctx); - notify_ssl_error_occurred(); - return 0; + int digest_size = EVP_MD_size(md); + /* digest_size < 0 iff EVP_MD context is NULL (which is impossible here) */ + assert(digest_size >= 0); + assert(digest_size <= (int)EVP_MAX_MD_SIZE); +#endif + /* digest_size == 0 means that the context is not entirely initialized */ + if (digest_size == 0) { + raise_ssl_error(PyExc_ValueError, "missing digest size"); + return BAD_DIGEST_SIZE; + } + return (unsigned int)digest_size; +} + +/* + * Extract the MAC value to 'buf' and return the digest size. + * + * The buffer 'buf' must have at least hashlib_openssl_HMAC_digest_size(self) + * bytes. Smaller buffers lead to undefined behaviors. + * + * On error, set an exception and return -1. + */ +static Py_ssize_t +hashlib_openssl_HMAC_digest_compute(HMACobject *self, unsigned char *buf) +{ + unsigned int digest_size = hashlib_openssl_HMAC_digest_size(self); + assert(digest_size <= EVP_MAX_MD_SIZE); + if (digest_size == BAD_DIGEST_SIZE) { + assert(PyErr_Occurred()); + return -1; + } + Py_HMAC_CTX_TYPE *ctx = hashlib_openssl_HMAC_ctx_copy_with_lock(self); + if (ctx == NULL) { + return -1; } - int r = HMAC_Final(temp_ctx, buf, &len); - HMAC_CTX_free(temp_ctx); +#ifdef Py_HAS_OPENSSL3_SUPPORT + int r = EVP_MAC_final(ctx, buf, NULL, digest_size); +#else + int r = HMAC_Final(ctx, buf, NULL); +#endif + hashlib_openssl_HMAC_ctx_free(ctx); if (r == 0) { notify_ssl_error_occurred(); - return 0; + return -1; } - return 1; + return digest_size; } /*[clinic input] @@ -1839,16 +2158,9 @@ static PyObject * _hashlib_HMAC_digest_impl(HMACobject *self) /*[clinic end generated code: output=1b1424355af7a41e input=bff07f74da318fb4]*/ { - unsigned char digest[EVP_MAX_MD_SIZE]; - unsigned int digest_size = _hashlib_hmac_digest_size(self); - if (digest_size == 0) { - return NULL; - } - int r = _hmac_digest(self, digest, digest_size); - if (r == 0) { - return NULL; - } - return PyBytes_FromStringAndSize((const char *)digest, digest_size); + unsigned char buf[EVP_MAX_MD_SIZE]; + Py_ssize_t n = hashlib_openssl_HMAC_digest_compute(self, buf); + return n < 0 ? NULL : PyBytes_FromStringAndSize((const char *)buf, n); } /*[clinic input] @@ -1864,49 +2176,48 @@ static PyObject * _hashlib_HMAC_hexdigest_impl(HMACobject *self) /*[clinic end generated code: output=80d825be1eaae6a7 input=5abc42702874ddcf]*/ { - unsigned char digest[EVP_MAX_MD_SIZE]; - unsigned int digest_size = _hashlib_hmac_digest_size(self); - if (digest_size == 0) { - return NULL; - } - int r = _hmac_digest(self, digest, digest_size); - if (r == 0) { - return NULL; - } - return _Py_strhex((const char *)digest, digest_size); + unsigned char buf[EVP_MAX_MD_SIZE]; + Py_ssize_t n = hashlib_openssl_HMAC_digest_compute(self, buf); + return n < 0 ? NULL : _Py_strhex((const char *)buf, n); } static PyObject * -_hashlib_hmac_get_digest_size(PyObject *op, void *Py_UNUSED(closure)) +_hashlib_HMAC_get_digestsize(PyObject *op, void *Py_UNUSED(closure)) { HMACobject *self = HMACobject_CAST(op); - unsigned int digest_size = _hashlib_hmac_digest_size(self); - return digest_size == 0 ? NULL : PyLong_FromLong(digest_size); + unsigned int size = hashlib_openssl_HMAC_digest_size(self); + return size == BAD_DIGEST_SIZE ? NULL : PyLong_FromLong(size); } static PyObject * -_hashlib_hmac_get_block_size(PyObject *op, void *Py_UNUSED(closure)) +_hashlib_HMAC_get_blocksize(PyObject *op, void *Py_UNUSED(closure)) { HMACobject *self = HMACobject_CAST(op); - const EVP_MD *md = _hashlib_hmac_get_md(self); +#ifdef Py_HAS_OPENSSL3_SUPPORT + assert(self->ctx != NULL); + return PyLong_FromSize_t(EVP_MAC_CTX_get_block_size(self->ctx)); +#else + const EVP_MD *md = hashlib_openssl_HMAC_evp_md_borrowed(self); return md == NULL ? NULL : PyLong_FromLong(EVP_MD_block_size(md)); +#endif } static PyObject * -_hashlib_hmac_get_name(PyObject *op, void *Py_UNUSED(closure)) +_hashlib_HMAC_get_name(PyObject *op, void *Py_UNUSED(closure)) { 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); + const char *digest_name = NULL; +#ifdef Py_HAS_OPENSSL3_SUPPORT + digest_name = get_hashlib_utf8name_by_nid(self->evp_md_nid); +#else + const EVP_MD *md = hashlib_openssl_HMAC_evp_md_borrowed(self); + digest_name = md == NULL ? NULL : get_hashlib_utf8name_by_evp_md(md); +#endif 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[] = { @@ -1917,15 +2228,15 @@ static PyMethodDef HMAC_methods[] = { {NULL, NULL} /* sentinel */ }; -static PyGetSetDef HMAC_getset[] = { - {"digest_size", _hashlib_hmac_get_digest_size, NULL, NULL, NULL}, - {"block_size", _hashlib_hmac_get_block_size, NULL, NULL, NULL}, - {"name", _hashlib_hmac_get_name, NULL, NULL, NULL}, +static PyGetSetDef HMAC_getsets[] = { + {"digest_size", _hashlib_HMAC_get_digestsize, NULL, NULL, NULL}, + {"block_size", _hashlib_HMAC_get_blocksize, NULL, NULL, NULL}, + {"name", _hashlib_HMAC_get_name, NULL, NULL, NULL}, {NULL} /* Sentinel */ }; -PyDoc_STRVAR(hmactype_doc, +PyDoc_STRVAR(HMACobject_type_doc, "The object used to calculate HMAC of a message.\n\ \n\ Methods:\n\ @@ -1940,20 +2251,24 @@ Attributes:\n\ name -- the name, including the hash algorithm used by this object\n\ digest_size -- number of bytes in digest() output\n"); -static PyType_Slot HMACtype_slots[] = { - {Py_tp_doc, (char *)hmactype_doc}, - {Py_tp_repr, _hmac_repr}, - {Py_tp_dealloc, _hmac_dealloc}, +static PyType_Slot HMACobject_type_slots[] = { + {Py_tp_doc, (char *)HMACobject_type_doc}, + {Py_tp_repr, _hashlib_HMAC_repr}, + {Py_tp_dealloc, _hashlib_HMAC_dealloc}, {Py_tp_methods, HMAC_methods}, - {Py_tp_getset, HMAC_getset}, + {Py_tp_getset, HMAC_getsets}, {0, NULL} }; -PyType_Spec HMACtype_spec = { - "_hashlib.HMAC", /* name */ - sizeof(HMACobject), /* basicsize */ - .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE, - .slots = HMACtype_slots, +PyType_Spec HMACobject_type_spec = { + .name = "_hashlib.HMAC", + .basicsize = sizeof(HMACobject), + .flags = ( + Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_DISALLOW_INSTANTIATION + | Py_TPFLAGS_IMMUTABLETYPE + ), + .slots = HMACobject_type_slots }; @@ -1982,10 +2297,13 @@ _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 { + } + else { if (PySet_Add(state->set, py_name) != 0) { state->error = 1; } @@ -2229,6 +2547,12 @@ hashlib_clear(PyObject *m) _Py_hashtable_destroy(state->hashtable); state->hashtable = NULL; } +#ifdef Py_HAS_OPENSSL3_SUPPORT + if (state->evp_hmac != NULL) { + EVP_MAC_free(state->evp_hmac); + state->evp_hmac = NULL; + } +#endif return 0; } @@ -2296,13 +2620,21 @@ hashlib_init_hmactype(PyObject *module) { _hashlibstate *state = get_hashlib_state(module); - state->HMACtype = (PyTypeObject *)PyType_FromSpec(&HMACtype_spec); + state->HMACtype = (PyTypeObject *)PyType_FromSpec(&HMACobject_type_spec); if (state->HMACtype == NULL) { return -1; } if (PyModule_AddType(module, state->HMACtype) < 0) { return -1; } +#ifdef Py_HAS_OPENSSL3_SUPPORT + state->evp_hmac = EVP_MAC_fetch(NULL, "HMAC", NULL); + if (state->evp_hmac == NULL) { + raise_ssl_error(PyExc_RuntimeError, "cannot initialize EVP_MAC HMAC"); + return -1; + } +#endif + return 0; } From eb3cb2ebdb64765d52c8fc3d77554b273490875c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 10:56:47 +0200 Subject: [PATCH 02/32] add helpers for setting exceptions --- Modules/_hashopenssl.c | 110 +++++++++++++++++++++++++++++++++++------ 1 file changed, 95 insertions(+), 15 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 42821ebe9f6a54..174855d020e672 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -311,8 +311,9 @@ class _hashlib.HMAC "HMACobject *" "((_hashlibstate *)PyModule_GetState(module)) /* Set an exception of given type using the given OpenSSL error code. */ static void -set_ssl_exception_from_errcode(PyObject *exc, unsigned long errcode) +set_ssl_exception_from_errcode(PyObject *exc_type, unsigned long errcode) { + assert(exc_type != NULL); assert(errcode != 0); /* ERR_ERROR_STRING(3) ensures that the messages below are ASCII */ @@ -321,13 +322,29 @@ set_ssl_exception_from_errcode(PyObject *exc, unsigned long errcode) const char *reason = ERR_reason_error_string(errcode); if (lib && func) { - PyErr_Format(exc, "[%s: %s] %s", lib, func, reason); + PyErr_Format(exc_type, "[%s: %s] %s", lib, func, reason); } else if (lib) { - PyErr_Format(exc, "[%s] %s", lib, reason); + PyErr_Format(exc_type, "[%s] %s", lib, reason); } else { - PyErr_SetString(exc, reason); + PyErr_SetString(exc_type, reason); + } +} + +/* + * Get an appropriate exception type for the given OpenSSL error code. + * + * The exception type depends on the error code reason. + */ +static PyObject * +get_smart_ssl_exception_type(unsigned long errcode, PyObject *default_exc_type) +{ + switch (ERR_GET_REASON(errcode)) { + case ERR_R_MALLOC_FAILURE: + return PyExc_MemoryError; + default: + return default_exc_type; } } @@ -335,36 +352,99 @@ set_ssl_exception_from_errcode(PyObject *exc, unsigned long errcode) * Set an exception of given type. * * By default, the exception's message is constructed by using the last SSL - * error that occurred. If no error occurred, the 'fallback_format' is used - * to create a C-style formatted fallback message. + * error that occurred. If no error occurred, the 'fallback_message' is used + * to create an exception message. */ static void -raise_ssl_error(PyObject *exc, const char *fallback_format, ...) +raise_ssl_error(PyObject *exc_type, const char *fallback_message) +{ + assert(fallback_message != NULL); + unsigned long errcode = ERR_peek_last_error(); + if (errcode) { + ERR_clear_error(); + set_ssl_exception_from_errcode(exc_type, errcode); + } + else { + PyErr_SetString(exc_type, fallback_message); + } +} + +/* Same as raise_ssl_error() with smart exception types. */ +static void +raise_smart_ssl_error(PyObject *exc_type, const char *fallback_message) +{ + assert(fallback_message != NULL); + unsigned long errcode = ERR_peek_last_error(); + if (errcode) { + ERR_clear_error(); + exc_type = get_smart_ssl_exception_type(errcode, exc_type); + set_ssl_exception_from_errcode(exc_type, errcode); + } + else { + PyErr_SetString(exc_type, fallback_message); + } +} + +/* Same as raise_ssl_error() but with a C-style formatted message. */ +static void +raise_ssl_error_f(PyObject *exc_type, const char *fallback_format, ...) { assert(fallback_format != NULL); unsigned long errcode = ERR_peek_last_error(); if (errcode) { ERR_clear_error(); - set_ssl_exception_from_errcode(exc, errcode); + set_ssl_exception_from_errcode(exc_type, errcode); } else { va_list vargs; va_start(vargs, fallback_format); - PyErr_FormatV(exc, fallback_format, vargs); + PyErr_FormatV(exc_type, fallback_format, vargs); + va_end(vargs); + } +} + +/* Same as raise_smart_ssl_error() but with a C-style formatted message. */ +static void +raise_smart_ssl_error_f(PyObject *exc_type, const char *fallback_format, ...) +{ + unsigned long errcode = ERR_peek_last_error(); + if (errcode) { + ERR_clear_error(); + exc_type = get_smart_ssl_exception_type(errcode, exc_type); + set_ssl_exception_from_errcode(exc_type, errcode); + } + else { + va_list vargs; + va_start(vargs, fallback_format); + PyErr_FormatV(exc_type, fallback_format, vargs); va_end(vargs); } } /* - * Set an exception with a generic default message after an error occurred. - * - * It can also be used without previous calls to SSL built-in functions, - * in which case a generic error message is provided. + * Raise a ValueError with a default message after an error occurred. + * It can also be used without previous calls to SSL built-in functions. */ static inline void -notify_ssl_error_occurred(void) +notify_ssl_error_occurred(const char *message) +{ + raise_ssl_error(PyExc_ValueError, message); +} + +/* Same as notify_ssl_error_occurred() for failed OpenSSL functions. */ +static inline void +notify_ssl_error_occurred_in(const char *funcname) +{ + raise_ssl_error_f(PyExc_ValueError, + "error in OpenSSL function: %s", funcname); +} + +/* Same as notify_smart_ssl_error_occurred() for failed OpenSSL functions. */ +static inline void +notify_smart_ssl_error_occurred_in(const char *funcname) { - raise_ssl_error(PyExc_ValueError, "no reason supplied"); + raise_smart_ssl_error_f(PyExc_ValueError, + "error in OpenSSL function %s", funcname); } /* LCOV_EXCL_STOP */ From e242865b95dbfd537a289dd9234eef3987232206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 10:57:17 +0200 Subject: [PATCH 03/32] refactor `get_openssl_evp_md_by_utf8name` error branches --- Modules/_hashopenssl.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 174855d020e672..869651f336420a 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -487,8 +487,7 @@ static PY_EVP_MD * get_openssl_evp_md_by_utf8name(PyObject *module, const char *name, Py_hash_type py_ht) { - PY_EVP_MD *digest = NULL; - PY_EVP_MD *other_digest = NULL; + PY_EVP_MD *digest = NULL, *other_digest = NULL; _hashlibstate *state = get_hashlib_state(module); py_hashentry_t *entry = (py_hashentry_t *)_Py_hashtable_get( state->hashtable, (const void*)name @@ -522,15 +521,16 @@ get_openssl_evp_md_by_utf8name(PyObject *module, const char *name, #endif } break; + default: + goto invalid_hash_type; } // if another thread same thing at same time make sure we got same ptr assert(other_digest == NULL || other_digest == digest); - if (digest != NULL) { - if (other_digest == NULL) { - PY_EVP_MD_up_ref(digest); - } + if (digest != NULL && other_digest == NULL) { + PY_EVP_MD_up_ref(digest); } - } else { + } + else { // Fall back for looking up an unindexed OpenSSL specific name. switch (py_ht) { case Py_ht_evp: @@ -541,14 +541,21 @@ get_openssl_evp_md_by_utf8name(PyObject *module, const char *name, case Py_ht_evp_nosecurity: digest = PY_EVP_MD_fetch(name, "-fips"); break; + default: + goto invalid_hash_type; } } if (digest == NULL) { - raise_ssl_error(state->unsupported_digestmod_error, - "unsupported hash type %s", name); + raise_ssl_error_f(state->unsupported_digestmod_error, + "EVP_MD_fetch: cannot fetch from %s", name); return NULL; } return digest; + +invalid_hash_type: + assert(digest == NULL); + PyErr_Format(PyExc_SystemError, "unsupported hash type %d", py_ht); + return NULL; } /* Get digest EVP_MD from object From ac6dea4dcd19d28282f3cfe37b315adb17d0dd0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:08:12 +0200 Subject: [PATCH 04/32] refactor `HASH.{digest,hexdigest}` computations --- Modules/_hashopenssl.c | 80 +++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 52 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 869651f336420a..e7c1b13f29236a 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -683,6 +683,30 @@ _hashlib_HASH_copy_impl(HASHobject *self) return (PyObject *)newobj; } +static Py_ssize_t +_hashlib_HASH_digest_compute(HASHobject *self, unsigned char *digest) +{ + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + if (ctx == NULL) { + notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_new)); + return -1; + } + if (_hashlib_HASH_copy_locked(self, ctx) < 0) { + goto error; + } + Py_ssize_t digest_size = EVP_MD_CTX_size(ctx); + if (!EVP_DigestFinal(ctx, digest, NULL)) { + notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_DigestFinal)); + goto error; + } + EVP_MD_CTX_free(ctx); + return digest_size; + +error: + EVP_MD_CTX_free(ctx); + return -1; +} + /*[clinic input] _hashlib.HASH.digest @@ -694,32 +718,8 @@ _hashlib_HASH_digest_impl(HASHobject *self) /*[clinic end generated code: output=3fc6f9671d712850 input=d8d528d6e50af0de]*/ { unsigned char digest[EVP_MAX_MD_SIZE]; - EVP_MD_CTX *temp_ctx; - PyObject *retval; - unsigned int digest_size; - - temp_ctx = EVP_MD_CTX_new(); - if (temp_ctx == NULL) { - PyErr_NoMemory(); - return NULL; - } - - if (!_hashlib_HASH_copy_locked(self, temp_ctx)) { - goto error; - } - digest_size = EVP_MD_CTX_size(temp_ctx); - if (!EVP_DigestFinal(temp_ctx, digest, NULL)) { - goto error; - } - - retval = PyBytes_FromStringAndSize((const char *)digest, digest_size); - EVP_MD_CTX_free(temp_ctx); - return retval; - -error: - EVP_MD_CTX_free(temp_ctx); - notify_ssl_error_occurred(); - return NULL; + Py_ssize_t n = _hashlib_HASH_digest_compute(self, digest); + return n < 0 ? NULL : PyBytes_FromStringAndSize((const char *)digest, n); } /*[clinic input] @@ -733,32 +733,8 @@ _hashlib_HASH_hexdigest_impl(HASHobject *self) /*[clinic end generated code: output=1b8e60d9711e7f4d input=ae7553f78f8372d8]*/ { unsigned char digest[EVP_MAX_MD_SIZE]; - EVP_MD_CTX *temp_ctx; - unsigned int digest_size; - - temp_ctx = EVP_MD_CTX_new(); - if (temp_ctx == NULL) { - PyErr_NoMemory(); - return NULL; - } - - /* Get the raw (binary) digest value */ - if (!_hashlib_HASH_copy_locked(self, temp_ctx)) { - goto error; - } - digest_size = EVP_MD_CTX_size(temp_ctx); - if (!EVP_DigestFinal(temp_ctx, digest, NULL)) { - goto error; - } - - EVP_MD_CTX_free(temp_ctx); - - return _Py_strhex((const char *)digest, (Py_ssize_t)digest_size); - -error: - EVP_MD_CTX_free(temp_ctx); - notify_ssl_error_occurred(); - return NULL; + Py_ssize_t n = _hashlib_HASH_digest_compute(self, digest); + return n < 0 ? NULL : _Py_strhex((const char *)digest, n); } /*[clinic input] From 4bc2808cad2148ed151eb3127aa224b8b8cf7e0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:09:24 +0200 Subject: [PATCH 05/32] refactor `_hashlib_HASH_copy_locked` and `locked_HMAC_CTX_copy` --- Modules/_hashopenssl.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index e7c1b13f29236a..997e9d2f778a00 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -655,7 +655,11 @@ _hashlib_HASH_copy_locked(HASHobject *self, EVP_MD_CTX *new_ctx_p) ENTER_HASHLIB(self); result = EVP_MD_CTX_copy(new_ctx_p, self->ctx); LEAVE_HASHLIB(self); - return result; + if (result == 0) { + notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_copy)); + return -1; + } + return 0; } /* External methods for a hash object */ @@ -675,7 +679,7 @@ _hashlib_HASH_copy_impl(HASHobject *self) if ((newobj = new_hash_object(Py_TYPE(self))) == NULL) return NULL; - if (!_hashlib_HASH_copy_locked(self, newobj->ctx)) { + if (_hashlib_HASH_copy_locked(self, newobj->ctx) < 0) { Py_DECREF(newobj); notify_ssl_error_occurred(); return NULL; @@ -899,7 +903,7 @@ _hashlib_HASHXOF_digest_impl(HASHobject *self, Py_ssize_t length) return NULL; } - if (!_hashlib_HASH_copy_locked(self, temp_ctx)) { + if (_hashlib_HASH_copy_locked(self, temp_ctx) < 0) { goto error; } if (!EVP_DigestFinalXOF(temp_ctx, @@ -949,7 +953,7 @@ _hashlib_HASHXOF_hexdigest_impl(HASHobject *self, Py_ssize_t length) } /* Get the raw (binary) digest value */ - if (!_hashlib_HASH_copy_locked(self, temp_ctx)) { + if (_hashlib_HASH_copy_locked(self, temp_ctx) < 0) { goto error; } if (!EVP_DigestFinalXOF(temp_ctx, digest, length)) { @@ -1736,7 +1740,11 @@ locked_HMAC_CTX_copy(HMAC_CTX *new_ctx_p, HMACobject *self) ENTER_HASHLIB(self); result = HMAC_CTX_copy(new_ctx_p, self->ctx); LEAVE_HASHLIB(self); - return result; + if (result == 0) { + notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_copy)); + return -1; + } + return 0; } /* returning 0 means that an error occurred and an exception is set */ @@ -1805,9 +1813,8 @@ _hashlib_HMAC_copy_impl(HMACobject *self) if (ctx == NULL) { return PyErr_NoMemory(); } - if (!locked_HMAC_CTX_copy(ctx, self)) { + if (locked_HMAC_CTX_copy(ctx, self) < 0) { HMAC_CTX_free(ctx); - notify_ssl_error_occurred(); return NULL; } @@ -1879,9 +1886,8 @@ _hmac_digest(HMACobject *self, unsigned char *buf, unsigned int len) (void)PyErr_NoMemory(); return 0; } - if (!locked_HMAC_CTX_copy(temp_ctx, self)) { + if (locked_HMAC_CTX_copy(temp_ctx, self) < 0) { HMAC_CTX_free(temp_ctx); - notify_ssl_error_occurred(); return 0; } int r = HMAC_Final(temp_ctx, buf, &len); From 14b87df1b0f7c63f9555b3e265baf085f11a787c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:09:49 +0200 Subject: [PATCH 06/32] set remaining exceptions after an OpenSSL failure --- Modules/_hashopenssl.c | 49 ++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 997e9d2f778a00..c0c5dbb7927427 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -439,7 +439,7 @@ notify_ssl_error_occurred_in(const char *funcname) "error in OpenSSL function: %s", funcname); } -/* Same as notify_smart_ssl_error_occurred() for failed OpenSSL functions. */ +/* Same as notify_ssl_error_occurred() with smart exception types. */ static inline void notify_smart_ssl_error_occurred_in(const char *funcname) { @@ -609,7 +609,7 @@ new_hash_object(PyTypeObject *type) retval->ctx = EVP_MD_CTX_new(); if (retval->ctx == NULL) { Py_DECREF(retval); - PyErr_NoMemory(); + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_new)); return NULL; } @@ -627,7 +627,7 @@ _hashlib_HASH_hash(HASHobject *self, const void *vp, Py_ssize_t len) else process = Py_SAFE_DOWNCAST(len, Py_ssize_t, unsigned int); if (!EVP_DigestUpdate(self->ctx, (const void*)cp, process)) { - notify_ssl_error_occurred(); + notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_DigestUpdate)); return -1; } len -= process; @@ -681,7 +681,6 @@ _hashlib_HASH_copy_impl(HASHobject *self) if (_hashlib_HASH_copy_locked(self, newobj->ctx) < 0) { Py_DECREF(newobj); - notify_ssl_error_occurred(); return NULL; } return (PyObject *)newobj; @@ -692,7 +691,7 @@ _hashlib_HASH_digest_compute(HASHobject *self, unsigned char *digest) { EVP_MD_CTX *ctx = EVP_MD_CTX_new(); if (ctx == NULL) { - notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_new)); + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_new)); return -1; } if (_hashlib_HASH_copy_locked(self, ctx) < 0) { @@ -809,7 +808,7 @@ _hashlib_HASH_get_name(PyObject *op, void *Py_UNUSED(closure)) HASHobject *self = HASHobject_CAST(op); const EVP_MD *md = EVP_MD_CTX_md(self->ctx); if (md == NULL) { - notify_ssl_error_occurred(); + notify_ssl_error_occurred("missing EVP_MD"); return NULL; } return get_openssl_evp_md_name(md); @@ -899,7 +898,7 @@ _hashlib_HASHXOF_digest_impl(HASHobject *self, Py_ssize_t length) temp_ctx = EVP_MD_CTX_new(); if (temp_ctx == NULL) { Py_DECREF(retval); - PyErr_NoMemory(); + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_new)); return NULL; } @@ -910,6 +909,7 @@ _hashlib_HASHXOF_digest_impl(HASHobject *self, Py_ssize_t length) (unsigned char*)PyBytes_AS_STRING(retval), length)) { + notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_DigestFinalXOF)); goto error; } @@ -919,7 +919,6 @@ _hashlib_HASHXOF_digest_impl(HASHobject *self, Py_ssize_t length) error: Py_DECREF(retval); EVP_MD_CTX_free(temp_ctx); - notify_ssl_error_occurred(); return NULL; } @@ -948,7 +947,7 @@ _hashlib_HASHXOF_hexdigest_impl(HASHobject *self, Py_ssize_t length) temp_ctx = EVP_MD_CTX_new(); if (temp_ctx == NULL) { PyMem_Free(digest); - PyErr_NoMemory(); + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_new)); return NULL; } @@ -957,6 +956,7 @@ _hashlib_HASHXOF_hexdigest_impl(HASHobject *self, Py_ssize_t length) goto error; } if (!EVP_DigestFinalXOF(temp_ctx, digest, length)) { + notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_DigestFinalXOF)); goto error; } @@ -969,7 +969,6 @@ _hashlib_HASHXOF_hexdigest_impl(HASHobject *self, Py_ssize_t length) error: PyMem_Free(digest); EVP_MD_CTX_free(temp_ctx); - notify_ssl_error_occurred(); return NULL; } @@ -1073,7 +1072,7 @@ _hashlib_HASH(PyObject *module, const char *digestname, PyObject *data_obj, int result = EVP_DigestInit_ex(self->ctx, digest, NULL); if (!result) { - notify_ssl_error_occurred(); + notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_DigestInit_ex)); Py_CLEAR(self); goto exit; } @@ -1482,7 +1481,7 @@ pbkdf2_hmac_impl(PyObject *module, const char *hash_name, if (!retval) { Py_CLEAR(key_obj); - notify_ssl_error_occurred(); + notify_ssl_error_occurred_in(Py_STRINGIFY(PKCS5_PBKDF2_HMAC)); goto end; } @@ -1580,7 +1579,7 @@ _hashlib_scrypt_impl(PyObject *module, Py_buffer *password, Py_buffer *salt, if (!retval) { Py_CLEAR(key_obj); - notify_ssl_error_occurred(); + notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_PBE_scrypt)); return NULL; } return key_obj; @@ -1637,7 +1636,7 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, PY_EVP_MD_free(evp); if (result == NULL) { - notify_ssl_error_occurred(); + notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC)); return NULL; } return PyBytes_FromStringAndSize((const char*)md, md_len); @@ -1698,14 +1697,14 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj, ctx = HMAC_CTX_new(); if (ctx == NULL) { PY_EVP_MD_free(digest); - PyErr_NoMemory(); + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_new)); goto error; } r = HMAC_Init_ex(ctx, key->buf, (int)key->len, digest, NULL /* impl */); PY_EVP_MD_free(digest); if (r == 0) { - notify_ssl_error_occurred(); + notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC_Init_ex)); goto error; } @@ -1791,7 +1790,7 @@ _hmac_update(HMACobject *self, PyObject *obj) PyBuffer_Release(&view); if (r == 0) { - notify_ssl_error_occurred(); + notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC_Update)); return 0; } return 1; @@ -1811,7 +1810,8 @@ _hashlib_HMAC_copy_impl(HMACobject *self) HMAC_CTX *ctx = HMAC_CTX_new(); if (ctx == NULL) { - return PyErr_NoMemory(); + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_new)); + return NULL; } if (locked_HMAC_CTX_copy(ctx, self) < 0) { HMAC_CTX_free(ctx); @@ -1883,7 +1883,7 @@ _hmac_digest(HMACobject *self, unsigned char *buf, unsigned int len) { HMAC_CTX *temp_ctx = HMAC_CTX_new(); if (temp_ctx == NULL) { - (void)PyErr_NoMemory(); + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_new)); return 0; } if (locked_HMAC_CTX_copy(temp_ctx, self) < 0) { @@ -1893,7 +1893,7 @@ _hmac_digest(HMACobject *self, unsigned char *buf, unsigned int len) int r = HMAC_Final(temp_ctx, buf, &len); HMAC_CTX_free(temp_ctx); if (r == 0) { - notify_ssl_error_occurred(); + notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC_Final)); return 0; } return 1; @@ -2113,16 +2113,13 @@ _hashlib_get_fips_mode_impl(PyObject *module) #else ERR_clear_error(); int result = FIPS_mode(); - if (result == 0) { + if (result == 0 && ERR_peek_last_error()) { // "If the library was built without support of the FIPS Object Module, // then the function will return 0 with an error code of // CRYPTO_R_FIPS_MODE_NOT_SUPPORTED (0x0f06d065)." // But 0 is also a valid result value. - unsigned long errcode = ERR_peek_last_error(); - if (errcode) { - notify_ssl_error_occurred(); - return -1; - } + notify_ssl_error_occurred(); + return -1; } return result; #endif From e5b2ef301bdc5177ff464ba09cf2c96e0bdb4c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:14:52 +0200 Subject: [PATCH 07/32] blurb --- .../next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst diff --git a/Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst b/Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst new file mode 100644 index 00000000000000..4ca59812dbe9dd --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst @@ -0,0 +1,2 @@ +:mod:`hashlib`: improve exception messages when an OpenSSL function failed. +Patch by Bénédikt Tran. From 5b412423de05bcf0df52256841355fbc15811133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:41:51 +0200 Subject: [PATCH 08/32] wrap OpenSSL context allocators --- Modules/_hashopenssl.c | 45 +++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index c0c5dbb7927427..c64d686b773417 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -597,6 +597,20 @@ get_openssl_evp_md(PyObject *module, PyObject *digestmod, return get_openssl_evp_md_by_utf8name(module, name, py_ht); } +// --- OpenSSL HASH wrappers -------------------------------------------------- + +static EVP_MD_CTX * +py_EVP_MD_CTX_new(void) +{ + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + if (ctx == NULL) { + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_new)); + } + return ctx; +} + +// --- HASH interface --------------------------------------------------------- + static HASHobject * new_hash_object(PyTypeObject *type) { @@ -606,10 +620,9 @@ new_hash_object(PyTypeObject *type) } HASHLIB_INIT_MUTEX(retval); - retval->ctx = EVP_MD_CTX_new(); + retval->ctx = py_EVP_MD_CTX_new(); if (retval->ctx == NULL) { Py_DECREF(retval); - notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_new)); return NULL; } @@ -689,9 +702,8 @@ _hashlib_HASH_copy_impl(HASHobject *self) static Py_ssize_t _hashlib_HASH_digest_compute(HASHobject *self, unsigned char *digest) { - EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + EVP_MD_CTX *ctx = py_EVP_MD_CTX_new(); if (ctx == NULL) { - notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_new)); return -1; } if (_hashlib_HASH_copy_locked(self, ctx) < 0) { @@ -895,10 +907,9 @@ _hashlib_HASHXOF_digest_impl(HASHobject *self, Py_ssize_t length) return NULL; } - temp_ctx = EVP_MD_CTX_new(); + temp_ctx = py_EVP_MD_CTX_new(); if (temp_ctx == NULL) { Py_DECREF(retval); - notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_new)); return NULL; } @@ -944,10 +955,9 @@ _hashlib_HASHXOF_hexdigest_impl(HASHobject *self, Py_ssize_t length) return NULL; } - temp_ctx = EVP_MD_CTX_new(); + temp_ctx = py_EVP_MD_CTX_new(); if (temp_ctx == NULL) { PyMem_Free(digest); - notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_new)); return NULL; } @@ -1645,6 +1655,16 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, /* OpenSSL-based HMAC implementation */ +static HMAC_CTX * +py_HMAC_CTX_new(void) +{ + HMAC_CTX *ctx = HMAC_CTX_new(); + if (ctx == NULL) { + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_new)); + } + return ctx; +} + static int _hmac_update(HMACobject*, PyObject*); static const EVP_MD * @@ -1694,10 +1714,9 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj, return NULL; } - ctx = HMAC_CTX_new(); + ctx = py_HMAC_CTX_new(); if (ctx == NULL) { PY_EVP_MD_free(digest); - notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_new)); goto error; } @@ -1808,9 +1827,8 @@ _hashlib_HMAC_copy_impl(HMACobject *self) { HMACobject *retval; - HMAC_CTX *ctx = HMAC_CTX_new(); + HMAC_CTX *ctx = py_HMAC_CTX_new(); if (ctx == NULL) { - notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_new)); return NULL; } if (locked_HMAC_CTX_copy(ctx, self) < 0) { @@ -1881,9 +1899,8 @@ _hashlib_HMAC_update_impl(HMACobject *self, PyObject *msg) static int _hmac_digest(HMACobject *self, unsigned char *buf, unsigned int len) { - HMAC_CTX *temp_ctx = HMAC_CTX_new(); + HMAC_CTX *temp_ctx = py_HMAC_CTX_new(); if (temp_ctx == NULL) { - notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_new)); return 0; } if (locked_HMAC_CTX_copy(temp_ctx, self) < 0) { From 2a8cf5555430ee3dfd475e23a02e39748c26beb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:50:12 +0200 Subject: [PATCH 09/32] fix compilation, remove unused functions and update messages --- Modules/_hashopenssl.c | 36 ++++++++++-------------------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index c64d686b773417..cb083e0449e0f8 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -369,22 +369,6 @@ raise_ssl_error(PyObject *exc_type, const char *fallback_message) } } -/* Same as raise_ssl_error() with smart exception types. */ -static void -raise_smart_ssl_error(PyObject *exc_type, const char *fallback_message) -{ - assert(fallback_message != NULL); - unsigned long errcode = ERR_peek_last_error(); - if (errcode) { - ERR_clear_error(); - exc_type = get_smart_ssl_exception_type(errcode, exc_type); - set_ssl_exception_from_errcode(exc_type, errcode); - } - else { - PyErr_SetString(exc_type, fallback_message); - } -} - /* Same as raise_ssl_error() but with a C-style formatted message. */ static void raise_ssl_error_f(PyObject *exc_type, const char *fallback_format, ...) @@ -403,7 +387,7 @@ raise_ssl_error_f(PyObject *exc_type, const char *fallback_format, ...) } } -/* Same as raise_smart_ssl_error() but with a C-style formatted message. */ +/* Same as raise_ssl_error_f() with smart exception types. */ static void raise_smart_ssl_error_f(PyObject *exc_type, const char *fallback_format, ...) { @@ -439,7 +423,7 @@ notify_ssl_error_occurred_in(const char *funcname) "error in OpenSSL function: %s", funcname); } -/* Same as notify_ssl_error_occurred() with smart exception types. */ +/* Same as notify_ssl_error_occurred_in() with smart exception types. */ static inline void notify_smart_ssl_error_occurred_in(const char *funcname) { @@ -669,7 +653,7 @@ _hashlib_HASH_copy_locked(HASHobject *self, EVP_MD_CTX *new_ctx_p) result = EVP_MD_CTX_copy(new_ctx_p, self->ctx); LEAVE_HASHLIB(self); if (result == 0) { - notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_copy)); + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_copy)); return -1; } return 0; @@ -820,7 +804,7 @@ _hashlib_HASH_get_name(PyObject *op, void *Py_UNUSED(closure)) HASHobject *self = HASHobject_CAST(op); const EVP_MD *md = EVP_MD_CTX_md(self->ctx); if (md == NULL) { - notify_ssl_error_occurred("missing EVP_MD"); + notify_ssl_error_occurred("missing EVP_MD for HASH context"); return NULL; } return get_openssl_evp_md_name(md); @@ -1567,8 +1551,8 @@ _hashlib_scrypt_impl(PyObject *module, Py_buffer *password, Py_buffer *salt, /* let OpenSSL validate the rest */ retval = EVP_PBE_scrypt(NULL, 0, NULL, 0, n, r, p, maxmem, NULL, 0); if (!retval) { - raise_ssl_error(PyExc_ValueError, - "Invalid parameter combination for n, r, p, maxmem."); + notify_ssl_error_occurred( + "Invalid parameter combination for n, r, p, maxmem."); return NULL; } @@ -1672,7 +1656,7 @@ _hashlib_hmac_get_md(HMACobject *self) { const EVP_MD *md = HMAC_CTX_get_md(self->ctx); if (md == NULL) { - raise_ssl_error(PyExc_ValueError, "missing EVP_MD for HMAC context"); + notify_ssl_error_occurred("missing EVP_MD for HMAC context"); } return md; } @@ -1759,7 +1743,7 @@ locked_HMAC_CTX_copy(HMAC_CTX *new_ctx_p, HMACobject *self) result = HMAC_CTX_copy(new_ctx_p, self->ctx); LEAVE_HASHLIB(self); if (result == 0) { - notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_copy)); + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_copy)); return -1; } return 0; @@ -1776,7 +1760,7 @@ _hashlib_hmac_digest_size(HMACobject *self) unsigned int digest_size = EVP_MD_size(md); assert(digest_size <= EVP_MAX_MD_SIZE); if (digest_size == 0) { - raise_ssl_error(PyExc_ValueError, "invalid digest size"); + notify_ssl_error_occurred("invalid digest size"); } return digest_size; } @@ -2135,7 +2119,7 @@ _hashlib_get_fips_mode_impl(PyObject *module) // then the function will return 0 with an error code of // CRYPTO_R_FIPS_MODE_NOT_SUPPORTED (0x0f06d065)." // But 0 is also a valid result value. - notify_ssl_error_occurred(); + notify_ssl_error_occurred_in(Py_STRINGIFY(FIPS_mode)); return -1; } return result; From ea2ce0eafc3cb4c1d5b883e5474e02efb0116cdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:53:12 +0200 Subject: [PATCH 10/32] update wrappers --- Modules/_hashopenssl.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index cb083e0449e0f8..848948c30a67cf 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -583,8 +583,9 @@ get_openssl_evp_md(PyObject *module, PyObject *digestmod, // --- OpenSSL HASH wrappers -------------------------------------------------- +/* Thin wrapper around EVP_MD_CTX_new() which sets an exception on failure. */ static EVP_MD_CTX * -py_EVP_MD_CTX_new(void) +py_wrapper_EVP_MD_CTX_new(void) { EVP_MD_CTX *ctx = EVP_MD_CTX_new(); if (ctx == NULL) { @@ -604,7 +605,7 @@ new_hash_object(PyTypeObject *type) } HASHLIB_INIT_MUTEX(retval); - retval->ctx = py_EVP_MD_CTX_new(); + retval->ctx = py_wrapper_EVP_MD_CTX_new(); if (retval->ctx == NULL) { Py_DECREF(retval); return NULL; @@ -686,7 +687,7 @@ _hashlib_HASH_copy_impl(HASHobject *self) static Py_ssize_t _hashlib_HASH_digest_compute(HASHobject *self, unsigned char *digest) { - EVP_MD_CTX *ctx = py_EVP_MD_CTX_new(); + EVP_MD_CTX *ctx = py_wrapper_EVP_MD_CTX_new(); if (ctx == NULL) { return -1; } @@ -891,7 +892,7 @@ _hashlib_HASHXOF_digest_impl(HASHobject *self, Py_ssize_t length) return NULL; } - temp_ctx = py_EVP_MD_CTX_new(); + temp_ctx = py_wrapper_EVP_MD_CTX_new(); if (temp_ctx == NULL) { Py_DECREF(retval); return NULL; @@ -939,7 +940,7 @@ _hashlib_HASHXOF_hexdigest_impl(HASHobject *self, Py_ssize_t length) return NULL; } - temp_ctx = py_EVP_MD_CTX_new(); + temp_ctx = py_wrapper_EVP_MD_CTX_new(); if (temp_ctx == NULL) { PyMem_Free(digest); return NULL; @@ -1639,8 +1640,9 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, /* OpenSSL-based HMAC implementation */ +/* Thin wrapper around HMAC_CTX_new() which sets an exception on failure. */ static HMAC_CTX * -py_HMAC_CTX_new(void) +py_openssl_wrapper_HMAC_CTX_new(void) { HMAC_CTX *ctx = HMAC_CTX_new(); if (ctx == NULL) { @@ -1698,7 +1700,7 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj, return NULL; } - ctx = py_HMAC_CTX_new(); + ctx = py_openssl_wrapper_HMAC_CTX_new(); if (ctx == NULL) { PY_EVP_MD_free(digest); goto error; @@ -1811,7 +1813,7 @@ _hashlib_HMAC_copy_impl(HMACobject *self) { HMACobject *retval; - HMAC_CTX *ctx = py_HMAC_CTX_new(); + HMAC_CTX *ctx = py_openssl_wrapper_HMAC_CTX_new(); if (ctx == NULL) { return NULL; } @@ -1883,7 +1885,7 @@ _hashlib_HMAC_update_impl(HMACobject *self, PyObject *msg) static int _hmac_digest(HMACobject *self, unsigned char *buf, unsigned int len) { - HMAC_CTX *temp_ctx = py_HMAC_CTX_new(); + HMAC_CTX *temp_ctx = py_openssl_wrapper_HMAC_CTX_new(); if (temp_ctx == NULL) { return 0; } From 43739c514d70fe80045f11530750b93104643a3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:57:18 +0200 Subject: [PATCH 11/32] align exception messages --- Modules/_hashopenssl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 848948c30a67cf..00889c5e72f4f9 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -420,7 +420,7 @@ static inline void notify_ssl_error_occurred_in(const char *funcname) { raise_ssl_error_f(PyExc_ValueError, - "error in OpenSSL function: %s", funcname); + "error in OpenSSL function %s()", funcname); } /* Same as notify_ssl_error_occurred_in() with smart exception types. */ @@ -428,7 +428,7 @@ static inline void notify_smart_ssl_error_occurred_in(const char *funcname) { raise_smart_ssl_error_f(PyExc_ValueError, - "error in OpenSSL function %s", funcname); + "error in OpenSSL function %s()", funcname); } /* LCOV_EXCL_STOP */ From e226431d0ba1b228318b37e0de42fc6d5f5be149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 12:13:16 +0200 Subject: [PATCH 12/32] context allocators can only set an ERR_R_MALLOC_FAILURE error --- Modules/_hashopenssl.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 00889c5e72f4f9..b81f668b2aa453 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -589,7 +589,8 @@ py_wrapper_EVP_MD_CTX_new(void) { EVP_MD_CTX *ctx = EVP_MD_CTX_new(); if (ctx == NULL) { - notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_new)); + PyErr_NoMemory(); + return NULL; } return ctx; } @@ -1646,7 +1647,8 @@ py_openssl_wrapper_HMAC_CTX_new(void) { HMAC_CTX *ctx = HMAC_CTX_new(); if (ctx == NULL) { - notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_new)); + PyErr_NoMemory(); + return NULL; } return ctx; } From 7201e0ad23dabdc41a2546d263c250a0c01bf74e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 12:22:21 +0200 Subject: [PATCH 13/32] update exception message for `get_openssl_evp_md_by_utf8name` --- Modules/_hashopenssl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index b81f668b2aa453..df96f70936f01f 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -531,7 +531,7 @@ get_openssl_evp_md_by_utf8name(PyObject *module, const char *name, } if (digest == NULL) { raise_ssl_error_f(state->unsupported_digestmod_error, - "EVP_MD_fetch: cannot fetch from %s", name); + "unsupported digest name: %s", name); return NULL; } return digest; From f8c2aed03f46fd460230ec51830aed690efdfcf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 12:37:43 +0200 Subject: [PATCH 14/32] refactor logic for mapping NIDs to EVP_MD objects --- Modules/_hashopenssl.c | 169 ++++++++++++++++++++++++++--------------- 1 file changed, 107 insertions(+), 62 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 42821ebe9f6a54..b553a681f5820a 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -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) @@ -464,6 +506,7 @@ 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; @@ -471,42 +514,46 @@ get_openssl_evp_md_by_utf8name(PyObject *module, const char *name, 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); } @@ -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[] = { @@ -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] @@ -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[] = { @@ -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 { From 64539c8c181c6ce967970c982419d5acf286968f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 12:50:19 +0200 Subject: [PATCH 15/32] fix(typo) --- Modules/_hashopenssl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index b553a681f5820a..f63e52ccc6d5f9 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -396,7 +396,7 @@ get_hashentry_by_nid(int nid) } /* - * Convert the NID to a string via OBJ_nid2_*() functions. + * Convert the NID to a string via OBJ_nid2*() functions. * * If 'nid' cannot be resolved, set an exception and return NULL. */ From 94858be987465c89fa57b1bade4fef7c70c043d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 13:34:29 +0200 Subject: [PATCH 16/32] post-merge --- Modules/_hashopenssl.c | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index b7ed7dd1ed4e6f..ccd6346480b4d2 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -433,40 +433,8 @@ raise_smart_ssl_error_f(PyObject *exc_type, const char *fallback_format, ...) } /* -<<<<<<< HEAD - * Same as raise_ssl_error() but raise a MemoryError - * if the last error reason is ERR_R_MALLOC_FAILURE. - */ -static void -raise_smart_ssl_error(PyObject *exc_type, const char *fallback_format, ...) -{ - - assert(fallback_format != NULL); - unsigned long errcode = ERR_peek_last_error(); - if (errcode) { - ERR_clear_error(); - set_ssl_exception_from_errcode( - get_smart_ssl_exception_type(errcode, exc_type), - errcode - ); - } - else { - va_list vargs; - va_start(vargs, fallback_format); - PyErr_FormatV(exc_type, fallback_format, vargs); - va_end(vargs); - } -} - -/* - * Raise an exception with a generic default message after an error occurred. - * - * It can also be used without previous calls to SSL built-in functions, - * in which case a generic error message is provided. -======= * Raise a ValueError with a default message after an error occurred. * It can also be used without previous calls to SSL built-in functions. ->>>>>>> feat/hashlib/smart-exceptions-135234 */ static inline void notify_ssl_error_occurred(const char *message) @@ -489,16 +457,6 @@ notify_smart_ssl_error_occurred_in(const char *funcname) raise_smart_ssl_error_f(PyExc_ValueError, "error in OpenSSL function %s()", funcname); } - -/* - * Same as notify_ssl_error_occurred() but raise a MemoryError - * if the last error reason is ERR_R_MALLOC_FAILURE. - */ -static inline void -notify_smart_ssl_error_occurred(void) -{ - raise_smart_ssl_error(PyExc_ValueError, "no reason supplied"); -} /* LCOV_EXCL_STOP */ /* From ecfb74a112036ee14d423bc4199f13aa3cdf50bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 13:36:32 +0200 Subject: [PATCH 17/32] remove useless macros --- Modules/_hashopenssl.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index ccd6346480b4d2..e9039e8f79c2ec 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -72,8 +72,6 @@ #define PY_EVP_MD_up_ref(md) EVP_MD_up_ref(md) #define PY_EVP_MD_free(md) EVP_MD_free(md) -#define EVP_MAC_INCREF(MAC) (void)EVP_MAC_up_ref(MAC) -#define EVP_MAC_DECREF(MAC) EVP_MAC_free(MAC) #define Py_HMAC_CTX_TYPE EVP_MAC_CTX #else #define PY_EVP_MD const EVP_MD @@ -81,8 +79,6 @@ #define PY_EVP_MD_up_ref(md) do {} while(0) #define PY_EVP_MD_free(md) do {} while(0) -#define EVP_MAC_INCREF(MAC) do {} while (0) -#define EVP_MAC_DECREF(MAC) do {} while (0) #define Py_HMAC_CTX_TYPE HMAC_CTX #endif From fc6b0b16a776595bc30e049dd7d72be365d45936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 13:37:11 +0200 Subject: [PATCH 18/32] remove useless directives --- Modules/_hashopenssl.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index e9039e8f79c2ec..8be91fc4bd0e75 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -82,10 +82,6 @@ #define Py_HMAC_CTX_TYPE HMAC_CTX #endif -#ifdef Py_HAS_OPENSSL3_SUPPORT -#else -#endif - /* hash alias map and fast lookup * * Map between Python's preferred names and OpenSSL internal names. Maintain From 661472c98c4bedd907c219dc1edb53554176772e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 13:56:46 +0200 Subject: [PATCH 19/32] post-merge --- Modules/_hashopenssl.c | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 8be91fc4bd0e75..448a4ee00a8602 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -1822,11 +1822,25 @@ hashlib_openssl_HMAC_evp_md_borrowed(HMACobject *self) } #endif +static int +hashlib_openssl_HMAC_update_once(Py_HMAC_CTX_TYPE *ctx, const Py_buffer *v) +{ + int r; #ifdef Py_HAS_OPENSSL3_SUPPORT -#define HASHLIB_OPENSSL_HMAC_UPDATE_ONCE EVP_MAC_update + r = EVP_MAC_update(ctx, (const unsigned char *)v->buf, (size_t)v->len); #else -#define HASHLIB_OPENSSL_HMAC_UPDATE_ONCE HMAC_Update + r = HMAC_Update(ctx, (const unsigned char *)v->buf, (size_t)v->len); #endif + if (r == 0) { +#ifdef Py_HAS_OPENSSL3_SUPPORT + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MAC_update)); +#else + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_Update)); +#endif + return -1; + } + return 0; +} static void hashlib_openssl_HMAC_ctx_free(Py_HMAC_CTX_TYPE *ctx) @@ -1854,30 +1868,21 @@ hashlib_openssl_HMAC_update_with_lock(HMACobject *self, PyObject *data) Py_buffer view = {0}; GET_BUFFER_VIEW_OR_ERROR(data, &view, return -1); if (!self->use_mutex && view.len >= HASHLIB_GIL_MINSIZE) { - // TODO(picnixz): disable mutex afterwards + // TODO(picnixz): see https://github.com/python/cpython/issues/135239. self->use_mutex = true; } if (self->use_mutex) { Py_BEGIN_ALLOW_THREADS PyMutex_Lock(&self->mutex); - r = HASHLIB_OPENSSL_HMAC_UPDATE_ONCE(self->ctx, - (const unsigned char *)view.buf, - (size_t)view.len); + r = hashlib_openssl_HMAC_update_once(self->ctx, &view); PyMutex_Unlock(&self->mutex); Py_END_ALLOW_THREADS } else { - r = HASHLIB_OPENSSL_HMAC_UPDATE_ONCE(self->ctx, - (const unsigned char *)view.buf, - (size_t)view.len); + r = hashlib_openssl_HMAC_update_once(self->ctx, &view); } PyBuffer_Release(&view); - if (r == 0) { - const char *funcname = Py_STRINGIFY(HASHLIB_OPENSSL_HMAC_UPDATE_ONCE); - notify_ssl_error_occurred_in(funcname); - return -1; - } - return 0; + return r; } static Py_HMAC_CTX_TYPE * @@ -1894,9 +1899,9 @@ hashlib_openssl_HMAC_ctx_copy_with_lock(HMACobject *self) } #else int r; - ctx = HMAC_CTX_new(); + ctx = py_openssl_wrapper_HMAC_CTX_new(); if (ctx == NULL) { - goto error; + return NULL; } ENTER_HASHLIB(self); r = HMAC_CTX_copy(ctx, self->ctx); From a837e21ad3f9f58c72724819ddfdfe8dbdf78e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 13:58:25 +0200 Subject: [PATCH 20/32] post-merge --- .../next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst diff --git a/Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst b/Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst new file mode 100644 index 00000000000000..4ca59812dbe9dd --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst @@ -0,0 +1,2 @@ +:mod:`hashlib`: improve exception messages when an OpenSSL function failed. +Patch by Bénédikt Tran. From 380f5a0c33af4c4b10802d012f07b4e15bfcef75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 14:01:42 +0200 Subject: [PATCH 21/32] Update Modules/_hashopenssl.c --- Modules/_hashopenssl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index b553a681f5820a..f63e52ccc6d5f9 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -396,7 +396,7 @@ get_hashentry_by_nid(int nid) } /* - * Convert the NID to a string via OBJ_nid2_*() functions. + * Convert the NID to a string via OBJ_nid2*() functions. * * If 'nid' cannot be resolved, set an exception and return NULL. */ From 7036df8aadb53908f012947565ee04b10feab067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 14:04:21 +0200 Subject: [PATCH 22/32] remove TODO --- Modules/_hashopenssl.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index f63e52ccc6d5f9..8f4b900bdedab5 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -506,7 +506,6 @@ 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; From a1066fe118caedd68f9806ca6321aadcce2f1513 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 14:21:39 +0200 Subject: [PATCH 23/32] pull main --- Modules/_hashopenssl.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 7a5f203fced742..1deef507a6575d 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -472,16 +472,19 @@ get_asn1_utf8name_by_nid(int 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; + goto error; } // 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); + goto error; } } return name; + +error: + raise_ssl_error_f(PyExc_ValueError, "cannot resolve NID %d", nid); + return NULL; } /* From 1a311e74be6bce834400729f6a47567660869b60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 9 Jun 2025 09:22:12 +0200 Subject: [PATCH 24/32] post merge --- .../Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst b/Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst index 06cb93b0468f1f..e1c11e46735f0b 100644 --- a/Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst +++ b/Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst @@ -1,7 +1,3 @@ :mod:`hashlib`: improve exception messages when an OpenSSL function failed. -<<<<<<< HEAD -Patch by Bénédikt Tran. -======= When memory allocation fails on OpenSSL's side, a :exc:`MemoryError` is raised instead of a :exc:`ValueError`. Patch by Bénédikt Tran. ->>>>>>> main From e64e6646cfd56c313a7899c002da3c28dd5f1146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 9 Jun 2025 09:46:59 +0200 Subject: [PATCH 25/32] post-merge --- Modules/_hashopenssl.c | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 6a713d52143fa5..13a0be8d509f40 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -33,16 +33,16 @@ # define Py_HAS_OPENSSL3_SUPPORT #endif +#include /* EVP is the preferred interface to hashing in OpenSSL */ #include #include // FIPS_mode() /* We use the object interface to discover what hashes OpenSSL supports. */ #include -#include #ifdef Py_HAS_OPENSSL3_SUPPORT -# include // OSSL_*_PARAM_* -# include // OSSL_PARAM_construct_*() +# include // OSSL_MAC_PARAM_DIGEST +# include // OSSL_PARAM_*() #else # include // HMAC() #endif @@ -1710,7 +1710,7 @@ _hashlib_scrypt_impl(PyObject *module, Py_buffer *password, Py_buffer *salt, #ifdef Py_HAS_OPENSSL3_SUPPORT /* EVP_MAC_CTX array of parameters specifying the "digest" */ -#define HASHLIB_HMAC_PARAMS(DIGEST) \ +#define HASHLIB_HMAC_OSSL_PARAMS(DIGEST) \ (const OSSL_PARAM []) { \ OSSL_PARAM_utf8_string(OSSL_MAC_PARAM_DIGEST, \ (char *)DIGEST, strlen(DIGEST)), \ @@ -1735,27 +1735,22 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, Py_buffer *msg, PyObject *digest) /*[clinic end generated code: output=82f19965d12706ac input=0a0790cc3db45c2e]*/ { + const void *r; unsigned char md[EVP_MAX_MD_SIZE] = {0}; #ifdef Py_HAS_OPENSSL3_SUPPORT size_t md_len = 0; -#else - unsigned int md_len = 0; -#endif - unsigned char *result; -#ifdef Py_HAS_OPENSSL3_SUPPORT const char *digest_name = NULL; #else - PY_EVP_MD *evp; + unsigned int md_len = 0; + PY_EVP_MD *evp = NULL; #endif if (key->len > INT_MAX) { - PyErr_SetString(PyExc_OverflowError, - "key is too long."); + PyErr_SetString(PyExc_OverflowError, "key is too long."); return NULL; } if (msg->len > INT_MAX) { - PyErr_SetString(PyExc_OverflowError, - "msg is too long."); + PyErr_SetString(PyExc_OverflowError, "msg is too long."); return NULL; } @@ -1765,9 +1760,9 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, return NULL; } Py_BEGIN_ALLOW_THREADS - result = EVP_Q_mac( + r = EVP_Q_mac( NULL, OSSL_MAC_NAME_HMAC, NULL, NULL, - HASHLIB_HMAC_PARAMS(digest_name), + HASHLIB_HMAC_OSSL_PARAMS(digest_name), (const void *)key->buf, (size_t)key->len, (const unsigned char *)msg->buf, (size_t)msg->len, md, sizeof(md), &md_len @@ -1781,7 +1776,7 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, } Py_BEGIN_ALLOW_THREADS - result = HMAC( + r = HMAC( evp, (const void *)key->buf, (int)key->len, (const unsigned char *)msg->buf, (size_t)msg->len, @@ -1790,9 +1785,12 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, Py_END_ALLOW_THREADS PY_EVP_MD_free(evp); #endif - - if (result == NULL) { + if (r == NULL) { +#ifdef Py_HAS_OPENSSL3_SUPPORT + notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_Q_mac)); +#else notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC)); +#endif return NULL; } return PyBytes_FromStringAndSize((const char*)md, md_len); @@ -1994,7 +1992,7 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj, ctx, (const unsigned char *)key->buf, (size_t)key->len, - HASHLIB_HMAC_PARAMS(digest) + HASHLIB_HMAC_OSSL_PARAMS(digest) ); #else PY_EVP_MD *digest = get_openssl_evp_md(module, digestmod, Py_ht_mac); From 9d44b31932399c020af559d53b8764bf8529e53a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 9 Jun 2025 11:11:25 +0200 Subject: [PATCH 26/32] remove un-necessary macros --- Modules/_hashopenssl.c | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 13a0be8d509f40..cf921411e28f40 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -73,6 +73,7 @@ #define PY_EVP_MD_free(md) EVP_MD_free(md) #define Py_HMAC_CTX_TYPE EVP_MAC_CTX +#define PY_HMAC_CTX_free EVP_MAC_CTX_free #else #define PY_EVP_MD const EVP_MD #define PY_EVP_MD_fetch(algorithm, properties) EVP_get_digestbyname(algorithm) @@ -80,6 +81,7 @@ #define PY_EVP_MD_free(md) do {} while(0) #define Py_HMAC_CTX_TYPE HMAC_CTX +#define PY_HMAC_CTX_free HMAC_CTX_free #endif /* hash alias map and fast lookup @@ -1843,25 +1845,16 @@ hashlib_openssl_HMAC_update_once(Py_HMAC_CTX_TYPE *ctx, const Py_buffer *v) return 0; } -static void -hashlib_openssl_HMAC_ctx_free(Py_HMAC_CTX_TYPE *ctx) +/* Thin wrapper around PY_HMAC_CTX_free that allows to pass a NULL 'ctx'. */ +static inline void +hashlib_openssl_HMAC_CTX_free(Py_HMAC_CTX_TYPE *ctx) { /* The NULL check was not present in every OpenSSL versions. */ if (ctx) { -#ifdef Py_HAS_OPENSSL3_SUPPORT - EVP_MAC_CTX_free(ctx); -#else - HMAC_CTX_free(ctx); -#endif + PY_HMAC_CTX_free(ctx); } } -#define hashlib_openssl_HMAC_ctx_clear(CTX) \ - do { \ - hashlib_openssl_HMAC_ctx_free(CTX); \ - CTX = NULL; \ - } while (0) - static int hashlib_openssl_HMAC_update_with_lock(HMACobject *self, PyObject *data) { @@ -1915,7 +1908,7 @@ hashlib_openssl_HMAC_ctx_copy_with_lock(HMACobject *self) return ctx; error: - hashlib_openssl_HMAC_ctx_free(ctx); + hashlib_openssl_HMAC_CTX_free(ctx); return NULL; } @@ -2039,7 +2032,7 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj, return (PyObject *)self; error: - hashlib_openssl_HMAC_ctx_free(ctx); + hashlib_openssl_HMAC_CTX_free(ctx); Py_XDECREF(self); return NULL; } @@ -2061,7 +2054,7 @@ _hashlib_HMAC_copy_impl(HMACobject *self) } retval = PyObject_New(HMACobject, Py_TYPE(self)); if (retval == NULL) { - hashlib_openssl_HMAC_ctx_free(ctx); + hashlib_openssl_HMAC_CTX_free(ctx); return NULL; } retval->ctx = ctx; @@ -2074,7 +2067,10 @@ _hashlib_HMAC_dealloc(PyObject *op) { HMACobject *self = HMACobject_CAST(op); PyTypeObject *tp = Py_TYPE(self); - hashlib_openssl_HMAC_ctx_clear(self->ctx); + if (self->ctx != NULL) { + PY_HMAC_CTX_free(self->ctx); + self->ctx = NULL; + } PyObject_Free(self); Py_DECREF(tp); } @@ -2173,7 +2169,7 @@ hashlib_openssl_HMAC_digest_compute(HMACobject *self, unsigned char *buf) #else int r = HMAC_Final(ctx, buf, NULL); #endif - hashlib_openssl_HMAC_ctx_free(ctx); + hashlib_openssl_HMAC_CTX_free(ctx); if (r == 0) { #ifdef Py_HAS_OPENSSL3_SUPPORT notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_MAC_final)); From 8b64ad6b5f2b904e806ee4a9098c6767f69634c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 9 Jun 2025 11:24:34 +0200 Subject: [PATCH 27/32] simplify some function calls --- Modules/_hashopenssl.c | 43 +++++++++++++++++------------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index cf921411e28f40..b0575202d38195 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -74,6 +74,7 @@ #define Py_HMAC_CTX_TYPE EVP_MAC_CTX #define PY_HMAC_CTX_free EVP_MAC_CTX_free +#define PY_HMAC_update EVP_MAC_update #else #define PY_EVP_MD const EVP_MD #define PY_EVP_MD_fetch(algorithm, properties) EVP_get_digestbyname(algorithm) @@ -82,6 +83,7 @@ #define Py_HMAC_CTX_TYPE HMAC_CTX #define PY_HMAC_CTX_free HMAC_CTX_free +#define PY_HMAC_update HMAC_Update #endif /* hash alias map and fast lookup @@ -1825,21 +1827,22 @@ hashlib_openssl_HMAC_evp_md_borrowed(HMACobject *self) } #endif -static int -hashlib_openssl_HMAC_update_once(Py_HMAC_CTX_TYPE *ctx, const Py_buffer *v) +static const char * +hashlib_HMAC_get_hashlib_digest_name(HMACobject *self) { - int r; -#ifdef Py_HAS_OPENSSL3_SUPPORT - r = EVP_MAC_update(ctx, (const unsigned char *)v->buf, (size_t)v->len); -#else - r = HMAC_Update(ctx, (const unsigned char *)v->buf, (size_t)v->len); -#endif - if (r == 0) { #ifdef Py_HAS_OPENSSL3_SUPPORT - notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MAC_update)); + return get_hashlib_utf8name_by_nid(self->evp_md_nid); #else - notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_Update)); + const EVP_MD *md = hashlib_openssl_HMAC_evp_md_borrowed(self); + return md == NULL ? NULL : get_hashlib_utf8name_by_evp_md(md); #endif +} + +static int +hashlib_openssl_HMAC_update_once(Py_HMAC_CTX_TYPE *ctx, const Py_buffer *v) +{ + if (!PY_HMAC_update(ctx, (const unsigned char *)v->buf, (size_t)v->len)) { + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(PY_HMAC_update)); return -1; } return 0; @@ -2054,7 +2057,7 @@ _hashlib_HMAC_copy_impl(HMACobject *self) } retval = PyObject_New(HMACobject, Py_TYPE(self)); if (retval == NULL) { - hashlib_openssl_HMAC_CTX_free(ctx); + PY_HMAC_CTX_free(ctx); return NULL; } retval->ctx = ctx; @@ -2078,14 +2081,8 @@ _hashlib_HMAC_dealloc(PyObject *op) static PyObject * _hashlib_HMAC_repr(PyObject *op) { - const char *digest_name; HMACobject *self = HMACobject_CAST(op); -#ifdef Py_HAS_OPENSSL3_SUPPORT - digest_name = get_hashlib_utf8name_by_nid(self->evp_md_nid); -#else - const EVP_MD *md = hashlib_openssl_HMAC_evp_md_borrowed(self); - digest_name = md == NULL ? NULL : get_hashlib_utf8name_by_evp_md(md); -#endif + const char *digest_name = hashlib_HMAC_get_hashlib_digest_name(self); if (digest_name == NULL) { assert(PyErr_Occurred()); return NULL; @@ -2238,13 +2235,7 @@ static PyObject * _hashlib_HMAC_get_name(PyObject *op, void *Py_UNUSED(closure)) { HMACobject *self = HMACobject_CAST(op); - const char *digest_name = NULL; -#ifdef Py_HAS_OPENSSL3_SUPPORT - digest_name = get_hashlib_utf8name_by_nid(self->evp_md_nid); -#else - const EVP_MD *md = hashlib_openssl_HMAC_evp_md_borrowed(self); - digest_name = md == NULL ? NULL : get_hashlib_utf8name_by_evp_md(md); -#endif + const char *digest_name = hashlib_HMAC_get_hashlib_digest_name(self); if (digest_name == NULL) { assert(PyErr_Occurred()); return NULL; From 2307154e61a250c2e275882c4f062801a5860e3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 9 Jun 2025 11:31:55 +0200 Subject: [PATCH 28/32] simplify call --- Modules/_hashopenssl.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index b0575202d38195..3fcdd9c077f751 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -2166,7 +2166,7 @@ hashlib_openssl_HMAC_digest_compute(HMACobject *self, unsigned char *buf) #else int r = HMAC_Final(ctx, buf, NULL); #endif - hashlib_openssl_HMAC_CTX_free(ctx); + PY_HMAC_CTX_free(ctx); if (r == 0) { #ifdef Py_HAS_OPENSSL3_SUPPORT notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_MAC_final)); @@ -2325,8 +2325,7 @@ _openssl_hash_name_mapper(const EVP_MD *md, const char *from, py_name = name == NULL ? NULL : PyUnicode_FromString(name); if (py_name == NULL) { state->error = 1; - } - else { + } else { if (PySet_Add(state->set, py_name) != 0) { state->error = 1; } From 90c4e945a9ecff6a4e1058316280b3fb98b7c4d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 9 Jun 2025 16:08:36 +0200 Subject: [PATCH 29/32] fix leak --- Modules/_hashopenssl.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 3fcdd9c077f751..90c7378617bceb 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -1975,10 +1975,13 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj, return NULL; } assert(evp_md_nid != NID_undef); - EVP_MAC_up_ref(state->evp_hmac); + /* + * OpenSSL is responsible for managing the EVP_MAC object's ref. count + * by calling EVP_MAC_up_ref() and EVP_MAC_free() in EVP_MAC_CTX_new() + * and EVP_MAC_CTX_free() respectively. + */ ctx = EVP_MAC_CTX_new(state->evp_hmac); if (ctx == NULL) { - EVP_MAC_free(state->evp_hmac); /* EVP_MAC_CTX_new() may also set an ERR_R_EVP_LIB error */ notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MAC_CTX_new)); return NULL; From 3640c0998d989ba0d0abe1458a0d1da899069bba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 9 Jun 2025 16:10:10 +0200 Subject: [PATCH 30/32] fix lint --- Modules/_hashopenssl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 90c7378617bceb..694dbc51a9e64e 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -1975,7 +1975,7 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj, return NULL; } assert(evp_md_nid != NID_undef); - /* + /* * OpenSSL is responsible for managing the EVP_MAC object's ref. count * by calling EVP_MAC_up_ref() and EVP_MAC_free() in EVP_MAC_CTX_new() * and EVP_MAC_CTX_free() respectively. From f1127a19ad8f0767ffc932844354debaf0ee24b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 14 Jun 2025 13:02:18 +0200 Subject: [PATCH 31/32] raise an ImportError when failing to fetch OpenSSL HMAC --- Modules/_hashopenssl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 694dbc51a9e64e..93fe41b1208e75 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -2652,7 +2652,7 @@ hashlib_init_hmactype(PyObject *module) #ifdef Py_HAS_OPENSSL3_SUPPORT state->evp_hmac = EVP_MAC_fetch(NULL, "HMAC", NULL); if (state->evp_hmac == NULL) { - raise_ssl_error(PyExc_RuntimeError, "cannot initialize EVP_MAC HMAC"); + raise_ssl_error(PyExc_ImportError, "cannot initialize EVP_MAC HMAC"); return -1; } #endif From 61792610706145665c5fd2be3cc3c1d6afb72191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 14 Jun 2025 13:02:50 +0200 Subject: [PATCH 32/32] raise an ImportError when failing to fetch OpenSSL HMAC --- Modules/_hashopenssl.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 93fe41b1208e75..5e63f68e23b83d 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -2652,7 +2652,8 @@ hashlib_init_hmactype(PyObject *module) #ifdef Py_HAS_OPENSSL3_SUPPORT state->evp_hmac = EVP_MAC_fetch(NULL, "HMAC", NULL); if (state->evp_hmac == NULL) { - raise_ssl_error(PyExc_ImportError, "cannot initialize EVP_MAC HMAC"); + ERR_clear_error(); + PyErr_SetString(PyExc_ImportError, "cannot initialize EVP_MAC HMAC"); return -1; } #endif