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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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 From e8fb3106204e274a5a58f1283b84e01a8fdae408 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, 22 Jun 2025 17:30:05 +0200 Subject: [PATCH 33/37] reduce diff --- Modules/_hashopenssl.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 1d027a2ad280cb..0f4168c3b55f7d 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -1735,7 +1735,7 @@ _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; + const void *result; unsigned char md[EVP_MAX_MD_SIZE] = {0}; #ifdef Py_HAS_OPENSSL3_SUPPORT size_t md_len = 0; @@ -1760,7 +1760,7 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, return NULL; } Py_BEGIN_ALLOW_THREADS - r = EVP_Q_mac( + result = EVP_Q_mac( NULL, OSSL_MAC_NAME_HMAC, NULL, NULL, HASHLIB_HMAC_OSSL_PARAMS(digest_name), (const void *)key->buf, (size_t)key->len, @@ -1776,7 +1776,7 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, } Py_BEGIN_ALLOW_THREADS - r = HMAC( + result = HMAC( evp, (const void *)key->buf, (int)key->len, (const unsigned char *)msg->buf, (size_t)msg->len, @@ -1785,7 +1785,7 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, Py_END_ALLOW_THREADS PY_EVP_MD_free(evp); #endif - if (r == NULL) { + if (result == NULL) { #ifdef Py_HAS_OPENSSL3_SUPPORT notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_Q_mac)); #else @@ -2200,7 +2200,7 @@ _hashlib_HMAC_hexdigest_impl(HMACobject *self) } static PyObject * -_hashlib_HMAC_get_digestsize(PyObject *op, void *Py_UNUSED(closure)) +_hashlib_HMAC_digest_size_getter(PyObject *op, void *Py_UNUSED(closure)) { HMACobject *self = HMACobject_CAST(op); unsigned int size = hashlib_openssl_HMAC_digest_size(self); @@ -2208,7 +2208,7 @@ _hashlib_HMAC_get_digestsize(PyObject *op, void *Py_UNUSED(closure)) } static PyObject * -_hashlib_HMAC_get_blocksize(PyObject *op, void *Py_UNUSED(closure)) +_hashlib_HMAC_block_size_getter(PyObject *op, void *Py_UNUSED(closure)) { HMACobject *self = HMACobject_CAST(op); #ifdef Py_HAS_OPENSSL3_SUPPORT @@ -2221,7 +2221,7 @@ _hashlib_HMAC_get_blocksize(PyObject *op, void *Py_UNUSED(closure)) } static PyObject * -_hashlib_HMAC_get_name(PyObject *op, void *Py_UNUSED(closure)) +_hashlib_HMAC_name_getter(PyObject *op, void *Py_UNUSED(closure)) { HMACobject *self = HMACobject_CAST(op); const char *digest_name = hashlib_HMAC_get_hashlib_digest_name(self); @@ -2241,9 +2241,9 @@ static PyMethodDef HMAC_methods[] = { }; 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}, + {"digest_size", _hashlib_HMAC_digest_size_getter, NULL, NULL, NULL}, + {"block_size", _hashlib_HMAC_block_size_getter, NULL, NULL, NULL}, + {"name", _hashlib_HMAC_name_getter, NULL, NULL, NULL}, {NULL} /* Sentinel */ }; From 4b3ff7de4ac01a5bf61ec222103a86ca1cd22f91 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, 23 Jun 2025 13:02:11 +0200 Subject: [PATCH 34/37] blurb --- .../Library/2025-06-23-13-02-08.gh-issue-134531.yUmj07.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-06-23-13-02-08.gh-issue-134531.yUmj07.rst diff --git a/Misc/NEWS.d/next/Library/2025-06-23-13-02-08.gh-issue-134531.yUmj07.rst b/Misc/NEWS.d/next/Library/2025-06-23-13-02-08.gh-issue-134531.yUmj07.rst new file mode 100644 index 00000000000000..032cf12e06f785 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-06-23-13-02-08.gh-issue-134531.yUmj07.rst @@ -0,0 +1,3 @@ +:mod:`hmac`: use the :manpage:`EVP_MAC(3ssl)` interface for HMAC when Python +is built with OpenSSL 3.0 and later instead of the deprecated +:manpage:`HMAC_CTX(3ssl) ` interface. Patch by Bénédikt Tran. From 36628f2dc14ee6e96f49d50b741195a5f013db6d 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, 20 Jul 2025 13:51:30 +0200 Subject: [PATCH 35/37] post-merge --- Lib/hashlib.py | 12 +- Lib/hmac.py | 14 ++ Lib/test/test_hashlib.py | 8 +- Lib/test/test_hmac.py | 2 +- Lib/test/test_remote_pdb.py | 53 ++++-- Modules/_hashopenssl.c | 371 +++++++++++++++++++++++++----------- Modules/hashlib.h | 9 + Modules/hmacmodule.c | 2 +- 8 files changed, 333 insertions(+), 138 deletions(-) diff --git a/Lib/hashlib.py b/Lib/hashlib.py index 6c72fba03bf687..02470ba0fdd559 100644 --- a/Lib/hashlib.py +++ b/Lib/hashlib.py @@ -80,6 +80,11 @@ } def __get_builtin_constructor(name): + if not isinstance(name, str): + # Since this function is only used by new(), we use the same + # exception as _hashlib.new() when 'name' is of incorrect type. + err = f"new() argument 'name' must be str, not {type(name).__name__}" + raise TypeError(err) cache = __builtin_constructor_cache constructor = cache.get(name) if constructor is not None: @@ -120,10 +125,13 @@ def __get_builtin_constructor(name): if constructor is not None: return constructor - raise ValueError('unsupported hash type ' + name) + # Keep the message in sync with hashlib.h::HASHLIB_UNSUPPORTED_ALGORITHM. + raise ValueError(f'unsupported hash algorithm {name}') def __get_openssl_constructor(name): + # This function is only used until the module has been initialized. + assert isinstance(name, str), "invalid call to __get_openssl_constructor()" if name in __block_openssl_constructor: # Prefer our builtin blake2 implementation. return __get_builtin_constructor(name) @@ -154,6 +162,8 @@ def __hash_new(name, *args, **kwargs): optionally initialized with data (which must be a bytes-like object). """ if name in __block_openssl_constructor: + # __block_openssl_constructor is expected to contain strings only + assert isinstance(name, str), f"unexpected name: {name}" # Prefer our builtin blake2 implementation. return __get_builtin_constructor(name)(*args, **kwargs) try: diff --git a/Lib/hmac.py b/Lib/hmac.py index 3683a4aa653a0a..e50d355fc60871 100644 --- a/Lib/hmac.py +++ b/Lib/hmac.py @@ -26,6 +26,16 @@ digest_size = None +def _is_shake_constructor(digest_like): + if isinstance(digest_like, str): + name = digest_like + else: + h = digest_like() if callable(digest_like) else digest_like.new() + if not isinstance(name := getattr(h, "name", None), str): + return False + return name.startswith(("shake", "SHAKE")) + + def _get_digest_constructor(digest_like): if callable(digest_like): return digest_like @@ -109,6 +119,8 @@ def _init_old(self, key, msg, digestmod): import warnings digest_cons = _get_digest_constructor(digestmod) + if _is_shake_constructor(digest_cons): + raise ValueError(f"unsupported hash algorithm {digestmod}") self._hmac = None self._outer = digest_cons() @@ -243,6 +255,8 @@ def digest(key, msg, digest): def _compute_digest_fallback(key, msg, digest): digest_cons = _get_digest_constructor(digest) + if _is_shake_constructor(digest_cons): + raise ValueError(f"unsupported hash algorithm {digest}") inner = digest_cons() outer = digest_cons() blocksize = getattr(inner, 'block_size', 64) diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py index 65e18639f82be5..7123641650263b 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -343,7 +343,9 @@ def test_clinic_signature_errors(self): def test_unknown_hash(self): self.assertRaises(ValueError, hashlib.new, 'spam spam spam spam spam') - self.assertRaises(TypeError, hashlib.new, 1) + # ensure that the exception message remains consistent + err = re.escape("new() argument 'name' must be str, not int") + self.assertRaisesRegex(TypeError, err, hashlib.new, 1) def test_new_upper_to_lower(self): self.assertEqual(hashlib.new("SHA256").name, "sha256") @@ -370,7 +372,9 @@ def test_get_builtin_constructor(self): sys.modules['_md5'] = _md5 else: del sys.modules['_md5'] - self.assertRaises(TypeError, get_builtin_constructor, 3) + # ensure that the exception message remains consistent + err = re.escape("new() argument 'name' must be str, not int") + self.assertRaises(TypeError, err, get_builtin_constructor, 3) constructor = get_builtin_constructor('md5') self.assertIs(constructor, _md5.md5) self.assertEqual(sorted(builtin_constructor_cache), ['MD5', 'md5']) diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py index ff6e1bce0ef801..02ded86678343f 100644 --- a/Lib/test/test_hmac.py +++ b/Lib/test/test_hmac.py @@ -960,7 +960,7 @@ def raiser(): with self.assertRaisesRegex(RuntimeError, "custom exception"): func(b'key', b'msg', raiser) - with self.assertRaisesRegex(ValueError, 'hash type'): + with self.assertRaisesRegex(ValueError, 'unsupported hash algorithm'): func(b'key', b'msg', 'unknown') with self.assertRaisesRegex(AttributeError, 'new'): diff --git a/Lib/test/test_remote_pdb.py b/Lib/test/test_remote_pdb.py index a1c50af15f3dd2..280e2444ef7d34 100644 --- a/Lib/test/test_remote_pdb.py +++ b/Lib/test/test_remote_pdb.py @@ -11,7 +11,7 @@ import unittest import unittest.mock from contextlib import closing, contextmanager, redirect_stdout, redirect_stderr, ExitStack -from test.support import is_wasi, cpython_only, force_color, requires_subprocess, SHORT_TIMEOUT +from test.support import is_wasi, cpython_only, force_color, requires_subprocess, SHORT_TIMEOUT, subTests from test.support.os_helper import TESTFN, unlink from typing import List @@ -279,37 +279,50 @@ def test_handling_other_message(self): expected_stdout="Some message.\n", ) - def test_handling_help_for_command(self): - """Test handling a request to display help for a command.""" + @unittest.skipIf(sys.flags.optimize >= 2, "Help not available for -OO") + @subTests( + "help_request,expected_substring", + [ + # a request to display help for a command + ({"help": "ll"}, "Usage: ll | longlist"), + # a request to display a help overview + ({"help": ""}, "type help "), + # a request to display the full PDB manual + ({"help": "pdb"}, ">>> import pdb"), + ], + ) + def test_handling_help_when_available(self, help_request, expected_substring): + """Test handling help requests when help is available.""" incoming = [ - ("server", {"help": "ll"}), + ("server", help_request), ] self.do_test( incoming=incoming, expected_outgoing=[], - expected_stdout_substring="Usage: ll | longlist", + expected_stdout_substring=expected_substring, ) - def test_handling_help_without_a_specific_topic(self): - """Test handling a request to display a help overview.""" + @unittest.skipIf(sys.flags.optimize < 2, "Needs -OO") + @subTests( + "help_request,expected_substring", + [ + # a request to display help for a command + ({"help": "ll"}, "No help for 'll'"), + # a request to display a help overview + ({"help": ""}, "Undocumented commands"), + # a request to display the full PDB manual + ({"help": "pdb"}, "No help for 'pdb'"), + ], + ) + def test_handling_help_when_not_available(self, help_request, expected_substring): + """Test handling help requests when help is not available.""" incoming = [ - ("server", {"help": ""}), + ("server", help_request), ] self.do_test( incoming=incoming, expected_outgoing=[], - expected_stdout_substring="type help ", - ) - - def test_handling_help_pdb(self): - """Test handling a request to display the full PDB manual.""" - incoming = [ - ("server", {"help": "pdb"}), - ] - self.do_test( - incoming=incoming, - expected_outgoing=[], - expected_stdout_substring=">>> import pdb", + expected_stdout_substring=expected_substring, ) def test_handling_pdb_prompts(self): diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index d655838c2ae20a..0699219d1652d8 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -89,6 +89,18 @@ #define PY_HMAC_update HMAC_Update #endif +/* + * Return 1 if *md* is an extendable-output Function (XOF) and 0 otherwise. + * SHAKE128 and SHAKE256 are XOF functions but not BLAKE2B algorithms. + * + * This is a backport of the EVP_MD_xof() helper added in OpenSSL 3.4. + */ +static inline int +PY_EVP_MD_xof(PY_EVP_MD *md) +{ + return md != NULL && ((EVP_MD_flags(md) & EVP_MD_FLAG_XOF) != 0); +} + /* hash alias map and fast lookup * * Map between Python's preferred names and OpenSSL internal names. Maintain @@ -342,6 +354,35 @@ py_wrapper_ERR_reason_error_string(unsigned long errcode) return reason ? reason : "no reason"; } +#ifdef Py_HAS_OPENSSL3_SUPPORT +/* + * Set an exception with additional information. + * + * This is only useful in OpenSSL 3.0 and later as the default reason + * usually lacks information and function locations are no longer encoded + * in the error code. + */ +static void +set_exception_with_ssl_errinfo(PyObject *exc_type, PyObject *exc_text, + const char *lib, const char *reason) +{ + assert(exc_type != NULL); + assert(exc_text != NULL); + if (lib && reason) { + PyErr_Format(exc_type, "[%s] %U (reason: %s)", lib, exc_text, reason); + } + else if (lib) { + PyErr_Format(exc_type, "[%s] %U", lib, exc_text); + } + else if (reason) { + PyErr_Format(exc_type, "%U (reason: %s)", exc_text, reason); + } + else { + PyErr_SetObject(exc_type, exc_text); + } +} +#endif + /* Set an exception of given type using the given OpenSSL error code. */ static void set_ssl_exception_from_errcode(PyObject *exc_type, unsigned long errcode) @@ -468,6 +509,68 @@ notify_smart_ssl_error_occurred_in(const char *funcname) raise_smart_ssl_error_f(PyExc_ValueError, "error in OpenSSL function %s()", funcname); } + +#ifdef Py_HAS_OPENSSL3_SUPPORT +static void +raise_unsupported_algorithm_impl(PyObject *exc_type, + const char *fallback_format, + const void *format_arg) +{ + // Since OpenSSL 3.0, if the algorithm is not supported or fetching fails, + // the reason lacks the algorithm name. + int errcode = ERR_peek_last_error(); + switch (ERR_GET_REASON(errcode)) { + case ERR_R_UNSUPPORTED: { + PyObject *text = PyUnicode_FromFormat(fallback_format, format_arg); + if (text != NULL) { + const char *lib = ERR_lib_error_string(errcode); + set_exception_with_ssl_errinfo(exc_type, text, lib, NULL); + Py_DECREF(text); + } + break; + } + case ERR_R_FETCH_FAILED: { + PyObject *text = PyUnicode_FromFormat(fallback_format, format_arg); + if (text != NULL) { + const char *lib = ERR_lib_error_string(errcode); + const char *reason = ERR_reason_error_string(errcode); + set_exception_with_ssl_errinfo(exc_type, text, lib, reason); + Py_DECREF(text); + } + break; + } + default: + raise_ssl_error_f(exc_type, fallback_format, format_arg); + break; + } + assert(PyErr_Occurred()); +} +#else +/* Before OpenSSL 3.0, error messages included enough information. */ +#define raise_unsupported_algorithm_impl raise_ssl_error_f +#endif + +static inline void +raise_unsupported_algorithm_error(_hashlibstate *state, PyObject *digestmod) +{ + raise_unsupported_algorithm_impl( + state->unsupported_digestmod_error, + HASHLIB_UNSUPPORTED_ALGORITHM, + digestmod + ); +} + +static inline void +raise_unsupported_str_algorithm_error(_hashlibstate *state, const char *name) +{ + raise_unsupported_algorithm_impl( + state->unsupported_digestmod_error, + HASHLIB_UNSUPPORTED_STR_ALGORITHM, + name + ); +} + +#undef raise_unsupported_algorithm_impl /* LCOV_EXCL_STOP */ /* @@ -559,26 +662,42 @@ get_hashlib_utf8name_by_evp_md(const EVP_MD *md) return get_hashlib_utf8name_by_nid(EVP_MD_nid(md)); } +/* + * Return 1 if the property query clause [1] must be "-fips" and 0 otherwise. + * + * [1] https://docs.openssl.org/master/man7/property + */ +static inline int +disable_fips_property(Py_hash_type py_ht) +{ + switch (py_ht) { + case Py_ht_evp: + case Py_ht_mac: + case Py_ht_pbkdf2: + return 0; + case Py_ht_evp_nosecurity: + return 1; + default: + Py_FatalError("unsupported hash type"); + } +} + /* * 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) +get_openssl_evp_md_by_utf8name(_hashlibstate *state, + const char *name, Py_hash_type py_ht) { 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 ); if (entry != NULL) { - switch (py_ht) { - case Py_ht_evp: - case Py_ht_mac: - case Py_ht_pbkdf2: + if (!disable_fips_property(py_ht)) { digest = FT_ATOMIC_LOAD_PTR_RELAXED(entry->evp); if (digest == NULL) { digest = PY_EVP_MD_fetch(entry->ossl_name, NULL); @@ -589,8 +708,8 @@ get_openssl_evp_md_by_utf8name(PyObject *module, const char *name, entry->evp = digest; #endif } - break; - case Py_ht_evp_nosecurity: + } + else { digest = FT_ATOMIC_LOAD_PTR_RELAXED(entry->evp_nosecurity); if (digest == NULL) { digest = PY_EVP_MD_fetch(entry->ossl_name, "-fips"); @@ -601,9 +720,6 @@ get_openssl_evp_md_by_utf8name(PyObject *module, const char *name, entry->evp_nosecurity = digest; #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); @@ -613,41 +729,15 @@ get_openssl_evp_md_by_utf8name(PyObject *module, const char *name, } else { // Fall back for looking up an unindexed OpenSSL specific name. - switch (py_ht) { - case Py_ht_evp: - case Py_ht_mac: - case Py_ht_pbkdf2: - digest = PY_EVP_MD_fetch(name, NULL); - break; - case Py_ht_evp_nosecurity: - digest = PY_EVP_MD_fetch(name, "-fips"); - break; - default: - goto invalid_hash_type; - } + const char *props = disable_fips_property(py_ht) ? "-fips" : NULL; + (void)props; // will only be used in OpenSSL 3.0 and later + digest = PY_EVP_MD_fetch(name, props); } if (digest == NULL) { - raise_ssl_error_f(state->unsupported_digestmod_error, - "unsupported digest name: %s", name); + raise_unsupported_str_algorithm_error(state, name); return NULL; } return digest; - -invalid_hash_type: - assert(digest == NULL); - PyErr_Format(PyExc_SystemError, "unsupported hash type %d", py_ht); - return NULL; -} - -/* - * 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); } /* @@ -661,27 +751,29 @@ raise_unsupported_digestmod_error(PyObject *module, PyObject *digestmod) * 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(_hashlibstate *state, + PyObject *digestmod, Py_hash_type py_ht) { const char *name; if (PyUnicode_Check(digestmod)) { name = PyUnicode_AsUTF8(digestmod); } else { - PyObject *dict = get_hashlib_state(module)->constructs; + PyObject *dict = state->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()) { - raise_unsupported_digestmod_error(module, digestmod); + raise_unsupported_algorithm_error(state, digestmod); } return NULL; } - return get_openssl_evp_md_by_utf8name(module, name, py_ht); + return get_openssl_evp_md_by_utf8name(state, name, py_ht); } +#ifdef Py_HAS_OPENSSL3_SUPPORT /* * Get the "canonical" name of an EVP_MD described by 'digestmod' and purpose. * @@ -690,26 +782,30 @@ get_openssl_evp_md(PyObject *module, PyObject *digestmod, Py_hash_type py_ht) * 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) +get_openssl_digest_name(_hashlibstate *state, + PyObject *digestmod, Py_hash_type py_ht, + EVP_MD **evp_md) { - if (evp_md_nid != NULL) { - *evp_md_nid = NID_undef; - } - PY_EVP_MD *md = get_openssl_evp_md(module, digestmod, py_ht); + PY_EVP_MD *md = get_openssl_evp_md(state, digestmod, py_ht); if (md == NULL) { return NULL; } 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); + if (evp_md != NULL) { + *evp_md = NULL; + } + PY_EVP_MD_free(md); + raise_unsupported_algorithm_error(state, digestmod); + return NULL; + } + if (evp_md != NULL) { + *evp_md = md; + } + else { + PY_EVP_MD_free(md); } return name; } @@ -1169,7 +1265,7 @@ static PyType_Spec HASHXOFobject_type_spec = { #endif static PyObject * -_hashlib_HASH(PyObject *module, const char *digestname, PyObject *data_obj, +_hashlib_HASH(_hashlibstate *state, const char *digestname, PyObject *data_obj, int usedforsecurity) { Py_buffer view = { 0 }; @@ -1182,16 +1278,16 @@ _hashlib_HASH(PyObject *module, const char *digestname, PyObject *data_obj, } digest = get_openssl_evp_md_by_utf8name( - module, digestname, usedforsecurity ? Py_ht_evp : Py_ht_evp_nosecurity + state, digestname, usedforsecurity ? Py_ht_evp : Py_ht_evp_nosecurity ); if (digest == NULL) { goto exit; } if ((EVP_MD_flags(digest) & EVP_MD_FLAG_XOF) == EVP_MD_FLAG_XOF) { - type = get_hashlib_state(module)->HASHXOF_type; + type = state->HASHXOF_type; } else { - type = get_hashlib_state(module)->HASH_type; + type = state->HASH_type; } self = new_hash_object(type); @@ -1245,7 +1341,8 @@ _hashlib_HASH(PyObject *module, const char *digestname, PyObject *data_obj, if (_Py_hashlib_data_argument(&data_obj, DATA, STRING) < 0) { \ return NULL; \ } \ - return _hashlib_HASH(MODULE, NAME, data_obj, USEDFORSECURITY); \ + _hashlibstate *state = get_hashlib_state(MODULE); \ + return _hashlib_HASH(state, NAME, data_obj, USEDFORSECURITY); \ } while (0) /* The module-level function: new() */ @@ -1547,12 +1644,13 @@ pbkdf2_hmac_impl(PyObject *module, const char *hash_name, PyObject *dklen_obj) /*[clinic end generated code: output=144b76005416599b input=ed3ab0d2d28b5d5c]*/ { + _hashlibstate *state = get_hashlib_state(module); PyObject *key_obj = NULL; char *key; long dklen; int retval; - PY_EVP_MD *digest = get_openssl_evp_md_by_utf8name(module, hash_name, Py_ht_pbkdf2); + PY_EVP_MD *digest = get_openssl_evp_md_by_utf8name(state, hash_name, Py_ht_pbkdf2); if (digest == NULL) { goto end; } @@ -1769,6 +1867,7 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, Py_buffer *msg, PyObject *digest) /*[clinic end generated code: output=82f19965d12706ac input=0a0790cc3db45c2e]*/ { + _hashlibstate *state = get_hashlib_state(module); const void *result; unsigned char md[EVP_MAX_MD_SIZE] = {0}; #ifdef Py_HAS_OPENSSL3_SUPPORT @@ -1776,8 +1875,9 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, const char *digest_name = NULL; #else unsigned int md_len = 0; - PY_EVP_MD *evp = NULL; #endif + int is_xof; + PY_EVP_MD *evp = NULL; if (key->len > INT_MAX) { PyErr_SetString(PyExc_OverflowError, "key is too long."); @@ -1789,10 +1889,13 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, } #ifdef Py_HAS_OPENSSL3_SUPPORT - digest_name = get_openssl_digest_name(module, digest, Py_ht_mac, NULL); + digest_name = get_openssl_digest_name(state, digest, Py_ht_mac, &evp); if (digest_name == NULL) { + assert(evp == NULL); return NULL; } + assert(evp != NULL); + is_xof = PY_EVP_MD_xof(evp); Py_BEGIN_ALLOW_THREADS result = EVP_Q_mac( NULL, OSSL_MAC_NAME_HMAC, NULL, NULL, @@ -1802,13 +1905,15 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, md, sizeof(md), &md_len ); Py_END_ALLOW_THREADS + PY_EVP_MD_free(evp); assert(md_len < (size_t)PY_SSIZE_T_MAX); #else - evp = get_openssl_evp_md(module, digest, Py_ht_mac); + evp = get_openssl_evp_md(state, digest, Py_ht_mac); if (evp == NULL) { return NULL; } + is_xof = PY_EVP_MD_xof(evp); Py_BEGIN_ALLOW_THREADS result = HMAC( evp, @@ -1820,11 +1925,17 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, PY_EVP_MD_free(evp); #endif if (result == NULL) { + if (is_xof) { + /* use a better default error message if an XOF is used */ + raise_unsupported_algorithm_error(state, digest); + } + else { #ifdef Py_HAS_OPENSSL3_SUPPORT - notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_Q_mac)); + notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_Q_mac)); #else - notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC)); + notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC)); #endif + } return NULL; } return PyBytes_FromStringAndSize((const char*)md, md_len); @@ -1935,40 +2046,17 @@ hashlib_openssl_HMAC_ctx_copy_with_lock(HMACobject *self) return NULL; } -/*[clinic input] -_hashlib.hmac_new - - key: Py_buffer - msg as msg_obj: object(c_default="NULL") = b'' - digestmod: object(c_default="NULL") = None - -Return a new hmac object. -[clinic start generated code]*/ - -static PyObject * -_hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj, - PyObject *digestmod) -/*[clinic end generated code: output=c20d9e4d9ed6d219 input=5f4071dcc7f34362]*/ +static Py_HMAC_CTX_TYPE * +hashlib_HMAC_CTX_new_from_digestmod(_hashlibstate *state, + Py_buffer *key, PyObject *digestmod, + int *nid) { - _hashlibstate *state = get_hashlib_state(module); - HMACobject *self = NULL; Py_HMAC_CTX_TYPE *ctx = NULL; + PY_EVP_MD *md = NULL; + int is_xof, r; #ifdef Py_HAS_OPENSSL3_SUPPORT - int evp_md_nid = NID_undef; + const char *digest = NULL; #endif - int r; - - if (key->len > INT_MAX) { - PyErr_SetString(PyExc_OverflowError, - "key is too long."); - return NULL; - } - - if (digestmod == NULL) { - PyErr_SetString(PyExc_TypeError, - "Missing required parameter 'digestmod'."); - return NULL; - } #ifdef Py_HAS_OPENSSL3_SUPPORT /* @@ -1988,13 +2076,15 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj, * 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 - ); + assert(nid != NULL); + digest = get_openssl_digest_name(state, digestmod, Py_ht_mac, &md); + assert((digest == NULL && md == NULL) || (digest != NULL && md != NULL)); if (digest == NULL) { return NULL; } - assert(evp_md_nid != NID_undef); + *nid = EVP_MD_nid(md); + is_xof = PY_EVP_MD_xof(md); + PY_EVP_MD_free(md); /* * 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() @@ -2014,26 +2104,81 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj, HASHLIB_HMAC_OSSL_PARAMS(digest) ); #else - PY_EVP_MD *digest = get_openssl_evp_md(module, digestmod, Py_ht_mac); - if (digest == NULL) { + assert(nid == NULL); + md = get_openssl_evp_md(module, digestmod, Py_ht_mac); + if (md == NULL) { return NULL; } ctx = py_openssl_wrapper_HMAC_CTX_new(); if (ctx == NULL) { - PY_EVP_MD_free(digest); + PY_EVP_MD_free(md); goto error; } - r = HMAC_Init_ex(ctx, key->buf, (int)key->len, digest, NULL /* impl */); - PY_EVP_MD_free(digest); + r = HMAC_Init_ex(ctx, key->buf, (int)key->len, md, NULL /* impl */); + is_xof = PY_EVP_MD_xof(md); + PY_EVP_MD_free(md); #endif if (r == 0) { + if (is_xof) { + /* use a better default error message if an XOF is used */ + raise_unsupported_algorithm_error(state, digestmod); + } + else { #ifdef Py_HAS_OPENSSL3_SUPPORT - notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_MAC_init)); + notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_MAC_init)); #else - notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC_Init_ex)); + notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC_Init_ex)); #endif - goto error; + } + return NULL; + } + return ctx; +} + +/*[clinic input] +_hashlib.hmac_new + + key: Py_buffer + msg as msg_obj: object(c_default="NULL") = b'' + digestmod: object(c_default="NULL") = None + +Return a new hmac object. +[clinic start generated code]*/ + +static PyObject * +_hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj, + PyObject *digestmod) +/*[clinic end generated code: output=c20d9e4d9ed6d219 input=5f4071dcc7f34362]*/ +{ + _hashlibstate *state = get_hashlib_state(module); + HMACobject *self = NULL; + Py_HMAC_CTX_TYPE *ctx = NULL; +#ifdef Py_HAS_OPENSSL3_SUPPORT + int nid; +#endif + + if (key->len > INT_MAX) { + PyErr_SetString(PyExc_OverflowError, + "key is too long."); + return NULL; + } + + if (digestmod == NULL) { + PyErr_SetString(PyExc_TypeError, + "Missing required parameter 'digestmod'."); + return NULL; + } + +#ifdef Py_HAS_OPENSSL3_SUPPORT + ctx = hashlib_HMAC_CTX_new_from_digestmod(state, key, digestmod, &nid); +#else + ctx = hashlib_HMAC_CTX_new_from_digestmod(state, key, digestmod, NULL); +#endif + + if (ctx == NULL) { + assert(PyErr_Occurred()); + return NULL; } self = PyObject_New(HMACobject, state->HMAC_type); @@ -2044,8 +2189,8 @@ _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; + assert(nid != NID_undef); + self->evp_md_nid = nid; #endif HASHLIB_INIT_MUTEX(self); diff --git a/Modules/hashlib.h b/Modules/hashlib.h index 9a7e72f34a7f9d..5de5922c345047 100644 --- a/Modules/hashlib.h +++ b/Modules/hashlib.h @@ -2,6 +2,15 @@ #include "pycore_lock.h" // PyMutex +/* + * Internal error messages used for reporting an unsupported hash algorithm. + * The algorithm can be given by its name, a callable or a PEP-247 module. + * The same message is raised by Lib/hashlib.py::__get_builtin_constructor() + * and _hmacmodule.c::find_hash_info(). + */ +#define HASHLIB_UNSUPPORTED_ALGORITHM "unsupported hash algorithm %S" +#define HASHLIB_UNSUPPORTED_STR_ALGORITHM "unsupported hash algorithm %s" + /* * Given a PyObject* obj, fill in the Py_buffer* viewp with the result * of PyObject_GetBuffer. Sets an exception and issues the erraction diff --git a/Modules/hmacmodule.c b/Modules/hmacmodule.c index 95e400231bb65c..b5405c99f1f8ce 100644 --- a/Modules/hmacmodule.c +++ b/Modules/hmacmodule.c @@ -656,7 +656,7 @@ find_hash_info(hmacmodule_state *state, PyObject *hash_info_ref) } if (rc == 0) { PyErr_Format(state->unknown_hash_error, - "unsupported hash type: %R", hash_info_ref); + HASHLIB_UNSUPPORTED_ALGORITHM, hash_info_ref); return NULL; } assert(info != NULL); From b1f44a806f73d686418392e3e557a4a907cf5cc1 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, 20 Jul 2025 15:47:12 +0200 Subject: [PATCH 36/37] post-merge --- Modules/_hashopenssl.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index deb5283e5c02a4..833750ad662bab 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -1889,6 +1889,7 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, } assert(evp != NULL); is_xof = PY_EVP_MD_xof(evp); + Py_BEGIN_ALLOW_THREADS result = EVP_Q_mac( NULL, OSSL_MAC_NAME_HMAC, NULL, NULL, @@ -1905,10 +1906,9 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, if (evp == NULL) { return NULL; } - is_xof = PY_EVP_MD_xof(evp); + Py_BEGIN_ALLOW_THREADS - is_xof = PY_EVP_MD_xof(evp); result = HMAC( evp, (const void *)key->buf, (int)key->len, @@ -2079,6 +2079,7 @@ hashlib_HMAC_CTX_new_from_digestmod(_hashlibstate *state, *nid = EVP_MD_nid(md); is_xof = PY_EVP_MD_xof(md); PY_EVP_MD_free(md); + /* * 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() @@ -2099,18 +2100,19 @@ hashlib_HMAC_CTX_new_from_digestmod(_hashlibstate *state, ); #else assert(nid == NULL); - md = get_openssl_evp_md(module, digestmod, Py_ht_mac); + md = get_openssl_evp_md(state, digestmod, Py_ht_mac); if (md == NULL) { return NULL; } + is_xof = PY_EVP_MD_xof(md); + ctx = py_openssl_wrapper_HMAC_CTX_new(); if (ctx == NULL) { PY_EVP_MD_free(md); - goto error; + return NULL; } r = HMAC_Init_ex(ctx, key->buf, (int)key->len, md, NULL /* impl */); - is_xof = PY_EVP_MD_xof(md); PY_EVP_MD_free(md); #endif if (r == 0) { From b3b4a6ab8383a83f821a8280d198c4baa1f7b77c 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, 26 Jul 2025 14:32:14 +0200 Subject: [PATCH 37/37] revert un-necessary cosmetics --- Modules/_hashopenssl.c | 109 +++++++++++++++++++---------------------- 1 file changed, 51 insertions(+), 58 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 833750ad662bab..1c07b26ca171fc 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -24,31 +24,27 @@ #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 - -#include /* EVP is the preferred interface to hashing in OpenSSL */ #include -#include // FIPS_mode() +#include // FIPS_mode() /* We use the object interface to discover what hashes OpenSSL supports. */ #include +#include -#ifdef Py_HAS_OPENSSL3_SUPPORT +#include + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +# define Py_HAS_OPENSSL3_SUPPORT # include // OSSL_MAC_PARAM_DIGEST # include // OSSL_PARAM_*() #else # include // HMAC() #endif -#include - #ifndef OPENSSL_THREADS # error "OPENSSL_THREADS is not defined, Python requires thread-safe OpenSSL" #endif @@ -73,7 +69,7 @@ #define PY_EVP_MD_CTX_md(CTX) EVP_MD_CTX_get0_md(CTX) -#define Py_HMAC_CTX_TYPE EVP_MAC_CTX +#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 @@ -84,7 +80,7 @@ #define PY_EVP_MD_CTX_md(CTX) EVP_MD_CTX_md(CTX) -#define Py_HMAC_CTX_TYPE HMAC_CTX +#define PY_HMAC_CTX_TYPE HMAC_CTX #define PY_HMAC_CTX_free HMAC_CTX_free #define PY_HMAC_update HMAC_Update #endif @@ -749,8 +745,7 @@ get_openssl_evp_md_by_utf8name(_hashlibstate *state, const char *name, * py_ht The message digest purpose. */ static PY_EVP_MD * -get_openssl_evp_md(_hashlibstate *state, - PyObject *digestmod, Py_hash_type py_ht) +get_openssl_evp_md(_hashlibstate *state, PyObject *digestmod, Py_hash_type py_ht) { const char *name; if (PyUnicode_Check(digestmod)) { @@ -1861,7 +1856,6 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, /*[clinic end generated code: output=82f19965d12706ac input=0a0790cc3db45c2e]*/ { _hashlibstate *state = get_hashlib_state(module); - const void *result; unsigned char md[EVP_MAX_MD_SIZE] = {0}; #ifdef Py_HAS_OPENSSL3_SUPPORT size_t md_len = 0; @@ -1869,15 +1863,18 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, #else unsigned int md_len = 0; #endif - int is_xof; + const void *result; PY_EVP_MD *evp = NULL; + int is_xof; 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; } @@ -1951,7 +1948,7 @@ py_openssl_wrapper_HMAC_CTX_new(void) } static const EVP_MD * -hashlib_openssl_HMAC_evp_md_borrowed(HMACobject *self) +_hashlib_hmac_get_md(HMACobject *self) { assert(self->ctx != NULL); const EVP_MD *md = HMAC_CTX_get_md(self->ctx); @@ -1968,13 +1965,13 @@ hashlib_HMAC_get_hashlib_digest_name(HMACobject *self) #ifdef Py_HAS_OPENSSL3_SUPPORT return get_hashlib_utf8name_by_nid(self->evp_md_nid); #else - const EVP_MD *md = hashlib_openssl_HMAC_evp_md_borrowed(self); + const EVP_MD *md = _hashlib_hmac_get_md(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) +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)); @@ -1985,7 +1982,7 @@ hashlib_openssl_HMAC_update_once(Py_HMAC_CTX_TYPE *ctx, const Py_buffer *v) /* 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) +hashlib_openssl_HMAC_CTX_free(PY_HMAC_CTX_TYPE *ctx) { /* The NULL check was not present in every OpenSSL versions. */ if (ctx) { @@ -2007,10 +2004,10 @@ hashlib_openssl_HMAC_update_with_lock(HMACobject *self, PyObject *data) return r; } -static Py_HMAC_CTX_TYPE * +static PY_HMAC_CTX_TYPE * hashlib_openssl_HMAC_ctx_copy_with_lock(HMACobject *self) { - Py_HMAC_CTX_TYPE *ctx = NULL; + PY_HMAC_CTX_TYPE *ctx = NULL; #ifdef Py_HAS_OPENSSL3_SUPPORT HASHLIB_ACQUIRE_LOCK(self); ctx = EVP_MAC_CTX_dup(self->ctx); @@ -2040,12 +2037,12 @@ hashlib_openssl_HMAC_ctx_copy_with_lock(HMACobject *self) return NULL; } -static Py_HMAC_CTX_TYPE * +static PY_HMAC_CTX_TYPE * hashlib_HMAC_CTX_new_from_digestmod(_hashlibstate *state, Py_buffer *key, PyObject *digestmod, int *nid) { - Py_HMAC_CTX_TYPE *ctx = NULL; + PY_HMAC_CTX_TYPE *ctx = NULL; PY_EVP_MD *md = NULL; int is_xof, r; #ifdef Py_HAS_OPENSSL3_SUPPORT @@ -2148,8 +2145,8 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj, /*[clinic end generated code: output=c20d9e4d9ed6d219 input=5f4071dcc7f34362]*/ { _hashlibstate *state = get_hashlib_state(module); + PY_HMAC_CTX_TYPE *ctx = NULL; HMACobject *self = NULL; - Py_HMAC_CTX_TYPE *ctx = NULL; #ifdef Py_HAS_OPENSSL3_SUPPORT int nid; #endif @@ -2215,7 +2212,7 @@ _hashlib_HMAC_copy_impl(HMACobject *self) /*[clinic end generated code: output=29aa28b452833127 input=e2fa6a05db61a4d6]*/ { HMACobject *retval; - Py_HMAC_CTX_TYPE *ctx = hashlib_openssl_HMAC_ctx_copy_with_lock(self); + PY_HMAC_CTX_TYPE *ctx = hashlib_openssl_HMAC_ctx_copy_with_lock(self); if (ctx == NULL) { return NULL; } @@ -2230,7 +2227,7 @@ _hashlib_HMAC_copy_impl(HMACobject *self) } static void -_hashlib_HMAC_dealloc(PyObject *op) +_hmac_dealloc(PyObject *op) { HMACobject *self = HMACobject_CAST(op); PyTypeObject *tp = Py_TYPE(self); @@ -2243,7 +2240,7 @@ _hashlib_HMAC_dealloc(PyObject *op) } static PyObject * -_hashlib_HMAC_repr(PyObject *op) +_hmac_repr(PyObject *op) { HMACobject *self = HMACobject_CAST(op); const char *digest_name = hashlib_HMAC_get_hashlib_digest_name(self); @@ -2287,7 +2284,7 @@ hashlib_openssl_HMAC_digest_size(HMACobject *self) 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); + const EVP_MD *md = _hashlib_hmac_get_md(self); if (md == NULL) { return BAD_DIGEST_SIZE; } @@ -2321,7 +2318,7 @@ hashlib_openssl_HMAC_digest_compute(HMACobject *self, unsigned char *buf) assert(PyErr_Occurred()); return -1; } - Py_HMAC_CTX_TYPE *ctx = hashlib_openssl_HMAC_ctx_copy_with_lock(self); + PY_HMAC_CTX_TYPE *ctx = hashlib_openssl_HMAC_ctx_copy_with_lock(self); if (ctx == NULL) { return -1; } @@ -2375,7 +2372,7 @@ _hashlib_HMAC_hexdigest_impl(HMACobject *self) } static PyObject * -_hashlib_HMAC_digest_size_getter(PyObject *op, void *Py_UNUSED(closure)) +_hashlib_hmac_get_digest_size(PyObject *op, void *Py_UNUSED(closure)) { HMACobject *self = HMACobject_CAST(op); unsigned int size = hashlib_openssl_HMAC_digest_size(self); @@ -2383,20 +2380,20 @@ _hashlib_HMAC_digest_size_getter(PyObject *op, void *Py_UNUSED(closure)) } static PyObject * -_hashlib_HMAC_block_size_getter(PyObject *op, void *Py_UNUSED(closure)) +_hashlib_hmac_get_block_size(PyObject *op, void *Py_UNUSED(closure)) { HMACobject *self = HMACobject_CAST(op); #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); + const EVP_MD *md = _hashlib_hmac_get_md(self); return md == NULL ? NULL : PyLong_FromLong(EVP_MD_block_size(md)); #endif } static PyObject * -_hashlib_HMAC_name_getter(PyObject *op, void *Py_UNUSED(closure)) +_hashlib_hmac_get_name(PyObject *op, void *Py_UNUSED(closure)) { HMACobject *self = HMACobject_CAST(op); const char *digest_name = hashlib_HMAC_get_hashlib_digest_name(self); @@ -2415,15 +2412,15 @@ static PyMethodDef HMAC_methods[] = { {NULL, NULL} /* sentinel */ }; -static PyGetSetDef HMAC_getsets[] = { - {"digest_size", _hashlib_HMAC_digest_size_getter, NULL, NULL, NULL}, - {"block_size", _hashlib_HMAC_block_size_getter, NULL, NULL, NULL}, - {"name", _hashlib_HMAC_name_getter, NULL, NULL, NULL}, +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}, {NULL} /* Sentinel */ }; -PyDoc_STRVAR(HMACobject_type_doc, +PyDoc_STRVAR(hmactype_doc, "The object used to calculate HMAC of a message.\n\ \n\ Methods:\n\ @@ -2438,24 +2435,20 @@ 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 HMACobject_type_slots[] = { - {Py_tp_doc, (char *)HMACobject_type_doc}, - {Py_tp_repr, _hashlib_HMAC_repr}, - {Py_tp_dealloc, _hashlib_HMAC_dealloc}, +static PyType_Slot HMACtype_slots[] = { + {Py_tp_doc, (char *)hmactype_doc}, + {Py_tp_repr, _hmac_repr}, + {Py_tp_dealloc, _hmac_dealloc}, {Py_tp_methods, HMAC_methods}, - {Py_tp_getset, HMAC_getsets}, + {Py_tp_getset, HMAC_getset}, {0, NULL} }; -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 +PyType_Spec HMACtype_spec = { + "_hashlib.HMAC", /* name */ + sizeof(HMACobject), /* basicsize */ + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE, + .slots = HMACtype_slots, }; @@ -2803,7 +2796,7 @@ hashlib_init_hmactype(PyObject *module) { _hashlibstate *state = get_hashlib_state(module); - state->HMAC_type = (PyTypeObject *)PyType_FromSpec(&HMACobject_type_spec); + state->HMAC_type = (PyTypeObject *)PyType_FromSpec(&HMACtype_spec); if (state->HMAC_type == NULL) { return -1; } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy