}
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:
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)
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:
#define PY_EVP_MD_CTX_md(CTX) EVP_MD_CTX_md(CTX)
#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
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)
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(), reason_id;
+ switch (reason_id = 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 */
/*
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 (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);
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");
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);
}
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);
}
/*
}
if (name == NULL) {
if (!PyErr_Occurred()) {
- raise_unsupported_digestmod_error(module, digestmod);
+ _hashlibstate *state = get_hashlib_state(module);
+ raise_unsupported_algorithm_error(state, digestmod);
}
return NULL;
}
unsigned int md_len = 0;
unsigned char *result;
PY_EVP_MD *evp;
+ int is_xof;
if (key->len > INT_MAX) {
PyErr_SetString(PyExc_OverflowError,
}
Py_BEGIN_ALLOW_THREADS
+ is_xof = PY_EVP_MD_xof(evp);
result = HMAC(
evp,
(const void *)key->buf, (int)key->len,
PY_EVP_MD_free(evp);
if (result == NULL) {
- notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC));
+ if (is_xof) {
+ _hashlibstate *state = get_hashlib_state(module);
+ /* use a better default error message if an XOF is used */
+ raise_unsupported_algorithm_error(state, digest);
+ }
+ else {
+ notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC));
+ }
return NULL;
}
return PyBytes_FromStringAndSize((const char*)md, md_len);
PY_EVP_MD *digest;
HMAC_CTX *ctx = NULL;
HMACobject *self = NULL;
- int r;
+ int is_xof, r;
if (key->len > INT_MAX) {
PyErr_SetString(PyExc_OverflowError,
goto error;
}
+ is_xof = PY_EVP_MD_xof(digest);
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_in(Py_STRINGIFY(HMAC_Init_ex));
+ if (is_xof) {
+ _hashlibstate *state = get_hashlib_state(module);
+ /* use a better default error message if an XOF is used */
+ raise_unsupported_algorithm_error(state, digestmod);
+ }
+ else {
+ notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC_Init_ex));
+ }
goto error;
}