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; }