Python-checkins
Threads by month
- ----- 2025 -----
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2006 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2005 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2004 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2003 -----
- December
- November
- October
- September
- August
March 2025
- 1 participants
- 715 discussions
https://github.com/python/cpython/commit/b3c18bfd828ba90b9c712da74095c4a052…
commit: b3c18bfd828ba90b9c712da74095c4a052887655
branch: main
author: Yuki Kobayashi <drsuaimqjgar(a)gmail.com>
committer: encukou <encukou(a)gmail.com>
date: 2025-03-03T15:08:05+01:00
summary:
gh-130711: Document `PyBaseObject_Type` (GH-130712)
Co-authored-by: Peter Bierma <zintensitydev(a)gmail.com>
files:
M Doc/c-api/structures.rst
diff --git a/Doc/c-api/structures.rst b/Doc/c-api/structures.rst
index d333df397782e0..987bc167c68d6c 100644
--- a/Doc/c-api/structures.rst
+++ b/Doc/c-api/structures.rst
@@ -63,6 +63,11 @@ under :ref:`reference counting <countingrefs>`.
See documentation of :c:type:`PyVarObject` above.
+.. c:var:: PyTypeObject PyBaseObject_Type
+
+ The base class of all other objects, the same as :class:`object` in Python.
+
+
.. c:function:: int Py_Is(PyObject *x, PyObject *y)
Test if the *x* object is the *y* object, the same as ``x is y`` in Python.
1
0

March 3, 2025
https://github.com/python/cpython/commit/4f14b7e30c0243e81407a34968495301e8…
commit: 4f14b7e30c0243e81407a34968495301e829a033
branch: main
author: Samuel Tyler <fosslinux(a)aussies.space>
committer: malemburg <mal(a)lemburg.com>
date: 2025-03-03T14:59:46+01:00
summary:
gh-100388: Change undefined __DATE__ to the Unix epoch (#100389)
files:
A Misc/NEWS.d/next/Core_and_Builtins/2022-12-21-14-28-01.gh-issue-100388.vne8ky.rst
M Modules/getbuildinfo.c
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2022-12-21-14-28-01.gh-issue-100388.vne8ky.rst b/Misc/NEWS.d/next/Core_and_Builtins/2022-12-21-14-28-01.gh-issue-100388.vne8ky.rst
new file mode 100644
index 00000000000000..d859c5511e6dc1
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2022-12-21-14-28-01.gh-issue-100388.vne8ky.rst
@@ -0,0 +1,2 @@
+Fix the ``platform._sys_version()`` method when ``__DATE__`` is undefined at
+buildtime by changing default buildtime datetime string to the UNIX epoch.
diff --git a/Modules/getbuildinfo.c b/Modules/getbuildinfo.c
index 8d553d106c6ab5..b68f0f5cc56a44 100644
--- a/Modules/getbuildinfo.c
+++ b/Modules/getbuildinfo.c
@@ -13,7 +13,7 @@
#ifdef __DATE__
#define DATE __DATE__
#else
-#define DATE "xx/xx/xx"
+#define DATE "Jan 01 1970"
#endif
#endif
@@ -21,7 +21,7 @@
#ifdef __TIME__
#define TIME __TIME__
#else
-#define TIME "xx:xx:xx"
+#define TIME "00:00:00"
#endif
#endif
1
0

gh-129173: refactor `PyCodec_BackslashReplaceErrors` into separate functions (#129895)
by picnixz March 3, 2025
by picnixz March 3, 2025
March 3, 2025
https://github.com/python/cpython/commit/3146a25e97700374ec470361889f0adac6…
commit: 3146a25e97700374ec470361889f0adac6cedbec
branch: main
author: Bénédikt Tran <10796600+picnixz(a)users.noreply.github.com>
committer: picnixz <10796600+picnixz(a)users.noreply.github.com>
date: 2025-03-03T13:58:15+01:00
summary:
gh-129173: refactor `PyCodec_BackslashReplaceErrors` into separate functions (#129895)
The logic of `PyCodec_BackslashReplaceErrors` is now split into separate functions,
each of which handling a specific exception type.
files:
M Python/codecs.c
diff --git a/Python/codecs.c b/Python/codecs.c
index d5d9a4a8bcabb7..8cdebfa1b611ea 100644
--- a/Python/codecs.c
+++ b/Python/codecs.c
@@ -956,49 +956,18 @@ PyObject *PyCodec_XMLCharRefReplaceErrors(PyObject *exc)
return restuple;
}
-PyObject *PyCodec_BackslashReplaceErrors(PyObject *exc)
+
+// --- handler: 'backslashreplace' --------------------------------------------
+
+static PyObject *
+_PyCodec_BackslashReplaceUnicodeEncodeError(PyObject *exc)
{
PyObject *obj;
Py_ssize_t objlen, start, end, slen;
- if (PyObject_TypeCheck(exc, (PyTypeObject *)PyExc_UnicodeDecodeError)) {
- if (_PyUnicodeError_GetParams(exc,
- &obj, &objlen,
- &start, &end, &slen, true) < 0)
- {
- return NULL;
- }
- PyObject *res = PyUnicode_New(4 * slen, 127);
- if (res == NULL) {
- Py_DECREF(obj);
- return NULL;
- }
- Py_UCS1 *outp = PyUnicode_1BYTE_DATA(res);
- const unsigned char *p = (const unsigned char *)PyBytes_AS_STRING(obj);
- for (Py_ssize_t i = start; i < end; i++, outp += 4) {
- const unsigned char ch = p[i];
- outp[0] = '\\';
- outp[1] = 'x';
- outp[2] = Py_hexdigits[(ch >> 4) & 0xf];
- outp[3] = Py_hexdigits[ch & 0xf];
- }
- assert(_PyUnicode_CheckConsistency(res, 1));
- Py_DECREF(obj);
- return Py_BuildValue("(Nn)", res, end);
- }
-
- if (
- PyObject_TypeCheck(exc, (PyTypeObject *)PyExc_UnicodeEncodeError)
- || PyObject_TypeCheck(exc, (PyTypeObject *)PyExc_UnicodeTranslateError)
- ) {
- if (_PyUnicodeError_GetParams(exc,
- &obj, &objlen,
- &start, &end, &slen, false) < 0)
- {
- return NULL;
- }
- }
- else {
- wrong_exception_type(exc);
+ if (_PyUnicodeError_GetParams(exc,
+ &obj, &objlen,
+ &start, &end, &slen, false) < 0)
+ {
return NULL;
}
@@ -1035,6 +1004,65 @@ PyObject *PyCodec_BackslashReplaceErrors(PyObject *exc)
}
+static PyObject *
+_PyCodec_BackslashReplaceUnicodeDecodeError(PyObject *exc)
+{
+ PyObject *obj;
+ Py_ssize_t objlen, start, end, slen;
+ if (_PyUnicodeError_GetParams(exc,
+ &obj, &objlen,
+ &start, &end, &slen, true) < 0)
+ {
+ return NULL;
+ }
+
+ PyObject *res = PyUnicode_New(4 * slen, 127);
+ if (res == NULL) {
+ Py_DECREF(obj);
+ return NULL;
+ }
+
+ Py_UCS1 *outp = PyUnicode_1BYTE_DATA(res);
+ const unsigned char *p = (const unsigned char *)PyBytes_AS_STRING(obj);
+ for (Py_ssize_t i = start; i < end; i++, outp += 4) {
+ const unsigned char ch = p[i];
+ outp[0] = '\\';
+ outp[1] = 'x';
+ outp[2] = Py_hexdigits[(ch >> 4) & 0xf];
+ outp[3] = Py_hexdigits[ch & 0xf];
+ }
+ assert(_PyUnicode_CheckConsistency(res, 1));
+ Py_DECREF(obj);
+ return Py_BuildValue("(Nn)", res, end);
+}
+
+
+static inline PyObject *
+_PyCodec_BackslashReplaceUnicodeTranslateError(PyObject *exc)
+{
+ // Same implementation as for UnicodeEncodeError objects.
+ return _PyCodec_BackslashReplaceUnicodeEncodeError(exc);
+}
+
+
+PyObject *PyCodec_BackslashReplaceErrors(PyObject *exc)
+{
+ if (_PyIsUnicodeEncodeError(exc)) {
+ return _PyCodec_BackslashReplaceUnicodeEncodeError(exc);
+ }
+ else if (_PyIsUnicodeDecodeError(exc)) {
+ return _PyCodec_BackslashReplaceUnicodeDecodeError(exc);
+ }
+ else if (_PyIsUnicodeTranslateError(exc)) {
+ return _PyCodec_BackslashReplaceUnicodeTranslateError(exc);
+ }
+ else {
+ wrong_exception_type(exc);
+ return NULL;
+ }
+}
+
+
// --- handler: 'namereplace' -------------------------------------------------
PyObject *PyCodec_NameReplaceErrors(PyObject *exc)
@@ -1502,7 +1530,8 @@ xmlcharrefreplace_errors(PyObject *Py_UNUSED(self), PyObject *exc)
}
-static PyObject *backslashreplace_errors(PyObject *self, PyObject *exc)
+static inline PyObject *
+backslashreplace_errors(PyObject *Py_UNUSED(self), PyObject *exc)
{
return PyCodec_BackslashReplaceErrors(exc);
}
1
0

[3.13] gh-130740: Move some `stdbool.h` includes after `Python.h` (#130738) (#130756)
by picnixz March 3, 2025
by picnixz March 3, 2025
March 3, 2025
https://github.com/python/cpython/commit/00cf2a621a21357b09bc7ae6f0ce0b799b…
commit: 00cf2a621a21357b09bc7ae6f0ce0b799b1fa17f
branch: 3.13
author: Bénédikt Tran <10796600+picnixz(a)users.noreply.github.com>
committer: picnixz <10796600+picnixz(a)users.noreply.github.com>
date: 2025-03-03T13:09:59+01:00
summary:
[3.13] gh-130740: Move some `stdbool.h` includes after `Python.h` (#130738) (#130756)
gh-130740: Move some `stdbool.h` includes after `Python.h` (#130738)
Move some `#include <stdbool.h>` after `#include "Python.h"` when `pyconfig.h` is not
included first and when we are in a platform-agnostic context. This is to avoid having
features defined by `stdbool.h` before those decided by `Python.h` (this caused some
build failures when compiling CPython with `zig cc`).
(cherry-picked from commit 214562ed4ddc248b007f718ed92ebcc0c3669611)
---------
Co-authored-by: Hugo Beauzée-Luyssen <hugo(a)beauzee.fr>
files:
A Misc/NEWS.d/next/Build/2025-03-01-18-27-42.gh-issue-130740.nDFSHR.rst
M Modules/_blake2/blake2b_impl.c
M Modules/_blake2/blake2s_impl.c
M Modules/_hashopenssl.c
M Parser/string_parser.c
M Python/flowgraph.c
M Python/instruction_sequence.c
diff --git a/Misc/NEWS.d/next/Build/2025-03-01-18-27-42.gh-issue-130740.nDFSHR.rst b/Misc/NEWS.d/next/Build/2025-03-01-18-27-42.gh-issue-130740.nDFSHR.rst
new file mode 100644
index 00000000000000..61d416c69f0c30
--- /dev/null
+++ b/Misc/NEWS.d/next/Build/2025-03-01-18-27-42.gh-issue-130740.nDFSHR.rst
@@ -0,0 +1,2 @@
+Ensure that ``Python.h`` is included before ``stdbool.h`` unless ``pyconfig.h``
+is included before or in some platform-specific contexts.
diff --git a/Modules/_blake2/blake2b_impl.c b/Modules/_blake2/blake2b_impl.c
index 0c3ae5a2fac275..370d01d55790b5 100644
--- a/Modules/_blake2/blake2b_impl.c
+++ b/Modules/_blake2/blake2b_impl.c
@@ -17,10 +17,11 @@
# define Py_BUILD_CORE_MODULE 1
#endif
-#include <stdbool.h>
#include "Python.h"
#include "pycore_strhex.h" // _Py_strhex()
+#include <stdbool.h>
+
#include "../hashlib.h"
#include "blake2module.h"
diff --git a/Modules/_blake2/blake2s_impl.c b/Modules/_blake2/blake2s_impl.c
index 3014773ab52331..0935866092c882 100644
--- a/Modules/_blake2/blake2s_impl.c
+++ b/Modules/_blake2/blake2s_impl.c
@@ -17,10 +17,11 @@
# define Py_BUILD_CORE_MODULE 1
#endif
-#include <stdbool.h>
#include "Python.h"
#include "pycore_strhex.h" // _Py_strhex()
+#include <stdbool.h>
+
#include "../hashlib.h"
#include "blake2module.h"
diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c
index fef0ed4a0b8844..7e54e6f4d0290c 100644
--- a/Modules/_hashopenssl.c
+++ b/Modules/_hashopenssl.c
@@ -22,7 +22,6 @@
# define Py_BUILD_CORE_MODULE 1
#endif
-#include <stdbool.h>
#include "Python.h"
#include "pycore_hashtable.h"
#include "pycore_pyhash.h" // _Py_HashBytes()
@@ -38,6 +37,7 @@
#include <openssl/objects.h>
#include <openssl/err.h>
+#include <stdbool.h>
#ifndef OPENSSL_THREADS
# error "OPENSSL_THREADS is not defined, Python requires thread-safe OpenSSL"
diff --git a/Parser/string_parser.c b/Parser/string_parser.c
index e92984935430ce..ce96b6e7b44f50 100644
--- a/Parser/string_parser.c
+++ b/Parser/string_parser.c
@@ -1,5 +1,3 @@
-#include <stdbool.h>
-
#include <Python.h>
#include "pycore_bytesobject.h" // _PyBytes_DecodeEscape()
#include "pycore_unicodeobject.h" // _PyUnicode_DecodeUnicodeEscapeInternal()
@@ -8,6 +6,8 @@
#include "pegen.h"
#include "string_parser.h"
+#include <stdbool.h>
+
//// STRING HANDLING FUNCTIONS ////
static int
diff --git a/Python/flowgraph.c b/Python/flowgraph.c
index ff70e47370241a..ecf510842ea748 100644
--- a/Python/flowgraph.c
+++ b/Python/flowgraph.c
@@ -1,6 +1,3 @@
-
-#include <stdbool.h>
-
#include "Python.h"
#include "pycore_flowgraph.h"
#include "pycore_compile.h"
@@ -9,6 +6,8 @@
#include "pycore_opcode_utils.h"
#include "pycore_opcode_metadata.h" // OPCODE_HAS_ARG, etc
+#include <stdbool.h>
+
#undef SUCCESS
#undef ERROR
diff --git a/Python/instruction_sequence.c b/Python/instruction_sequence.c
index a3f85f754d71bb..e713ebb9b8734e 100644
--- a/Python/instruction_sequence.c
+++ b/Python/instruction_sequence.c
@@ -5,8 +5,6 @@
*/
-#include <stdbool.h>
-
#include "Python.h"
#include "pycore_compile.h" // _PyCompile_EnsureArrayLargeEnough
@@ -22,6 +20,8 @@ typedef _Py_SourceLocation location;
#include "clinic/instruction_sequence.c.h"
+#include <stdbool.h>
+
#undef SUCCESS
#undef ERROR
#define SUCCESS 0
1
0
https://github.com/python/cpython/commit/04091c083340dde7d4eeb6d945c70f3b37…
commit: 04091c083340dde7d4eeb6d945c70f3b37d88f85
branch: main
author: Chris Withers <chris(a)withers.org>
committer: cjw296 <chris(a)withers.org>
date: 2025-03-03T11:44:59Z
summary:
`unittest.mock` test and coverage fixup (#130787)
* Mark functions that will never be called with # pragma: no cover
* Fix testpatch.PatchTest.test_exit_idempotent
.stop() and __exit__ have subtly different code paths, so to really test __exit__ idempotency, we need to call it specifically twice.
files:
M Lib/test/test_unittest/testmock/testhelpers.py
M Lib/test/test_unittest/testmock/testmock.py
M Lib/test/test_unittest/testmock/testpatch.py
diff --git a/Lib/test/test_unittest/testmock/testhelpers.py b/Lib/test/test_unittest/testmock/testhelpers.py
index 8d0f3ebc5cba88..d1e48bde982040 100644
--- a/Lib/test/test_unittest/testmock/testhelpers.py
+++ b/Lib/test/test_unittest/testmock/testhelpers.py
@@ -1080,7 +1080,7 @@ def test_dataclass_with_method(self):
class WithMethod:
a: int
def b(self) -> int:
- return 1
+ return 1 # pragma: no cover
for mock in [
create_autospec(WithMethod, instance=True),
diff --git a/Lib/test/test_unittest/testmock/testmock.py b/Lib/test/test_unittest/testmock/testmock.py
index 5d1bf4258afacd..386d53bf5a5c63 100644
--- a/Lib/test/test_unittest/testmock/testmock.py
+++ b/Lib/test/test_unittest/testmock/testmock.py
@@ -316,7 +316,7 @@ def test_explicit_return_value_even_if_mock_wraps_object(self):
passed to the wrapped object and the return_value is returned instead.
"""
def my_func():
- return None
+ return None # pragma: no cover
func_mock = create_autospec(spec=my_func, wraps=my_func)
return_value = "explicit return value"
func_mock.return_value = return_value
diff --git a/Lib/test/test_unittest/testmock/testpatch.py b/Lib/test/test_unittest/testmock/testpatch.py
index 7c5fc3deed2ca2..bd85fdcfc472a6 100644
--- a/Lib/test/test_unittest/testmock/testpatch.py
+++ b/Lib/test/test_unittest/testmock/testpatch.py
@@ -748,7 +748,7 @@ def test_stop_idempotent(self):
def test_exit_idempotent(self):
patcher = patch(foo_name, 'bar', 3)
with patcher:
- patcher.stop()
+ patcher.__exit__(None, None, None)
def test_second_start_failure(self):
1
0

March 3, 2025
https://github.com/python/cpython/commit/f693f8422700f889f90ab548ceeb24704a…
commit: f693f8422700f889f90ab548ceeb24704a2ae952
branch: main
author: Bénédikt Tran <10796600+picnixz(a)users.noreply.github.com>
committer: picnixz <10796600+picnixz(a)users.noreply.github.com>
date: 2025-03-03T11:43:22Z
summary:
gh-129173: simplify `PyCodec_XMLCharRefReplaceErrors` logic (#129894)
Writing the decimal representation of a Unicode codepoint only requires to know the number of digits.
---------
Co-authored-by: Petr Viktorin <encukou(a)gmail.com>
files:
M Python/codecs.c
diff --git a/Python/codecs.c b/Python/codecs.c
index b876b816f688a0..d5d9a4a8bcabb7 100644
--- a/Python/codecs.c
+++ b/Python/codecs.c
@@ -730,6 +730,25 @@ codec_handler_write_unicode_hex(Py_UCS1 **p, Py_UCS4 ch)
}
+/*
+ * Determine the number of digits for a decimal representation of Unicode
+ * codepoint 'ch' (by design, Unicode codepoints are limited to 7 digits).
+ */
+static inline int
+n_decimal_digits_for_codepoint(Py_UCS4 ch)
+{
+ if (ch < 10) return 1;
+ if (ch < 100) return 2;
+ if (ch < 1000) return 3;
+ if (ch < 10000) return 4;
+ if (ch < 100000) return 5;
+ if (ch < 1000000) return 6;
+ if (ch < 10000000) return 7;
+ // Unicode codepoints are limited to 1114111 (7 decimal digits)
+ Py_UNREACHABLE();
+}
+
+
/*
* Create a Unicode string containing 'count' copies of the official
* Unicode REPLACEMENT CHARACTER (0xFFFD).
@@ -867,9 +886,12 @@ PyObject *PyCodec_ReplaceErrors(PyObject *exc)
}
}
+
+// --- handler: 'xmlcharrefreplace' -------------------------------------------
+
PyObject *PyCodec_XMLCharRefReplaceErrors(PyObject *exc)
{
- if (!PyObject_TypeCheck(exc, (PyTypeObject *)PyExc_UnicodeEncodeError)) {
+ if (!_PyIsUnicodeEncodeError(exc)) {
wrong_exception_type(exc);
return NULL;
}
@@ -896,30 +918,11 @@ PyObject *PyCodec_XMLCharRefReplaceErrors(PyObject *exc)
Py_ssize_t ressize = 0;
for (Py_ssize_t i = start; i < end; ++i) {
- /* object is guaranteed to be "ready" */
Py_UCS4 ch = PyUnicode_READ_CHAR(obj, i);
- if (ch < 10) {
- ressize += 2 + 1 + 1;
- }
- else if (ch < 100) {
- ressize += 2 + 2 + 1;
- }
- else if (ch < 1000) {
- ressize += 2 + 3 + 1;
- }
- else if (ch < 10000) {
- ressize += 2 + 4 + 1;
- }
- else if (ch < 100000) {
- ressize += 2 + 5 + 1;
- }
- else if (ch < 1000000) {
- ressize += 2 + 6 + 1;
- }
- else {
- assert(ch < 10000000);
- ressize += 2 + 7 + 1;
- }
+ int k = n_decimal_digits_for_codepoint(ch);
+ assert(k != 0);
+ assert(k <= 7);
+ ressize += 2 + k + 1;
}
/* allocate replacement */
@@ -931,45 +934,20 @@ PyObject *PyCodec_XMLCharRefReplaceErrors(PyObject *exc)
Py_UCS1 *outp = PyUnicode_1BYTE_DATA(res);
/* generate replacement */
for (Py_ssize_t i = start; i < end; ++i) {
- int digits, base;
Py_UCS4 ch = PyUnicode_READ_CHAR(obj, i);
- if (ch < 10) {
- digits = 1;
- base = 1;
- }
- else if (ch < 100) {
- digits = 2;
- base = 10;
- }
- else if (ch < 1000) {
- digits = 3;
- base = 100;
- }
- else if (ch < 10000) {
- digits = 4;
- base = 1000;
- }
- else if (ch < 100000) {
- digits = 5;
- base = 10000;
- }
- else if (ch < 1000000) {
- digits = 6;
- base = 100000;
- }
- else {
- assert(ch < 10000000);
- digits = 7;
- base = 1000000;
- }
+ /*
+ * Write the decimal representation of 'ch' to the buffer pointed by 'p'
+ * using at most 7 characters prefixed by '&#' and suffixed by ';'.
+ */
*outp++ = '&';
*outp++ = '#';
- while (digits-- > 0) {
- assert(base >= 1);
- *outp++ = '0' + ch / base;
- ch %= base;
- base /= 10;
+ Py_UCS1 *digit_end = outp + n_decimal_digits_for_codepoint(ch);
+ for (Py_UCS1 *p_digit = digit_end - 1; p_digit >= outp; --p_digit) {
+ *p_digit = '0' + (ch % 10);
+ ch /= 10;
}
+ assert(ch == 0);
+ outp = digit_end;
*outp++ = ';';
}
assert(_PyUnicode_CheckConsistency(res, 1));
@@ -1517,7 +1495,8 @@ replace_errors(PyObject *Py_UNUSED(self), PyObject *exc)
}
-static PyObject *xmlcharrefreplace_errors(PyObject *self, PyObject *exc)
+static inline PyObject *
+xmlcharrefreplace_errors(PyObject *Py_UNUSED(self), PyObject *exc)
{
return PyCodec_XMLCharRefReplaceErrors(exc);
}
1
0
https://github.com/python/cpython/commit/efbc5929ca022661bd311b075116746d29…
commit: efbc5929ca022661bd311b075116746d294bd71b
branch: main
author: Ned Batchelder <ned(a)nedbatchelder.com>
committer: nedbat <ned(a)nedbatchelder.com>
date: 2025-03-03T06:38:49-05:00
summary:
docs: internal notes have moved, correct references (#130762)
files:
M Include/internal/pycore_code.h
M Python/assemble.c
diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h
index 6d45d5f0c4071f..fa0e0bd01c99e9 100644
--- a/Include/internal/pycore_code.h
+++ b/Include/internal/pycore_code.h
@@ -474,7 +474,7 @@ read_obj(uint16_t *p)
return val;
}
-/* See Objects/exception_handling_notes.txt for details.
+/* See InternalDocs/exception_handling.md for details.
*/
static inline unsigned char *
parse_varint(unsigned char *p, int *result) {
diff --git a/Python/assemble.c b/Python/assemble.c
index 6dcac332f076d8..070be1ca54e3ea 100644
--- a/Python/assemble.c
+++ b/Python/assemble.c
@@ -126,7 +126,7 @@ assemble_emit_exception_table_item(struct assembler *a, int value, int msb)
write_except_byte(a, (value&0x3f) | msb);
}
-/* See Objects/exception_handling_notes.txt for details of layout */
+/* See InternalDocs/exception_handling.md for details of layout */
#define MAX_SIZE_OF_ENTRY 20
static int
1
0

March 3, 2025
https://github.com/python/cpython/commit/9643ce9019c9f01a0e5d773d3abddb7947…
commit: 9643ce9019c9f01a0e5d773d3abddb79471fa19a
branch: 3.13
author: Bénédikt Tran <10796600+picnixz(a)users.noreply.github.com>
committer: picnixz <10796600+picnixz(a)users.noreply.github.com>
date: 2025-03-03T11:01:01Z
summary:
[3.13] gh-127667: fix memory leaks in `hashlib` (GH-127668) (#130784)
gh-127667: fix memory leaks in `hashlib` (GH-127668)
- Correctly handle `NULL` values returned by `EVP_MD_CTX_md`.
- Correctly free resources in error branches.
- Consistently suppress `_setException()` return value when needed.
- Collapse `_setException() + return NULL` into a single statement.
(cherry-picked from commit 097846502b7f33cb327d512e2a396acf4f4de46e)
files:
M Modules/_hashopenssl.c
diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c
index 2f7d277e07bac0..fef0ed4a0b8844 100644
--- a/Modules/_hashopenssl.c
+++ b/Modules/_hashopenssl.c
@@ -427,7 +427,8 @@ py_digest_by_name(PyObject *module, const char *name, enum Py_hash_type py_ht)
}
}
if (digest == NULL) {
- _setException(state->unsupported_digestmod_error, "unsupported hash type %s", name);
+ (void)_setException(state->unsupported_digestmod_error,
+ "unsupported hash type %s", name);
return NULL;
}
return digest;
@@ -442,7 +443,6 @@ py_digest_by_name(PyObject *module, const char *name, enum Py_hash_type py_ht)
*/
static PY_EVP_MD*
py_digest_by_digestmod(PyObject *module, PyObject *digestmod, enum Py_hash_type py_ht) {
- PY_EVP_MD* evp;
PyObject *name_obj = NULL;
const char *name;
@@ -468,12 +468,7 @@ py_digest_by_digestmod(PyObject *module, PyObject *digestmod, enum Py_hash_type
return NULL;
}
- evp = py_digest_by_name(module, name, py_ht);
- if (evp == NULL) {
- return NULL;
- }
-
- return evp;
+ return py_digest_by_name(module, name, py_ht);
}
static EVPobject *
@@ -506,7 +501,7 @@ EVP_hash(EVPobject *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)) {
- _setException(PyExc_ValueError, NULL);
+ (void)_setException(PyExc_ValueError, NULL);
return -1;
}
len -= process;
@@ -582,17 +577,20 @@ EVP_digest_impl(EVPobject *self)
}
if (!locked_EVP_MD_CTX_copy(temp_ctx, self)) {
- return _setException(PyExc_ValueError, NULL);
+ goto error;
}
digest_size = EVP_MD_CTX_size(temp_ctx);
if (!EVP_DigestFinal(temp_ctx, digest, NULL)) {
- _setException(PyExc_ValueError, NULL);
- return 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);
+ return _setException(PyExc_ValueError, NULL);
}
/*[clinic input]
@@ -617,17 +615,20 @@ EVP_hexdigest_impl(EVPobject *self)
/* Get the raw (binary) digest value */
if (!locked_EVP_MD_CTX_copy(temp_ctx, self)) {
- return _setException(PyExc_ValueError, NULL);
+ goto error;
}
digest_size = EVP_MD_CTX_size(temp_ctx);
if (!EVP_DigestFinal(temp_ctx, digest, NULL)) {
- _setException(PyExc_ValueError, NULL);
- return 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);
+ return _setException(PyExc_ValueError, NULL);
}
/*[clinic input]
@@ -695,7 +696,11 @@ EVP_get_digest_size(EVPobject *self, void *closure)
static PyObject *
EVP_get_name(EVPobject *self, void *closure)
{
- return py_digest_name(EVP_MD_CTX_md(self->ctx));
+ const EVP_MD *md = EVP_MD_CTX_md(self->ctx);
+ if (md == NULL) {
+ return _setException(PyExc_ValueError, NULL);
+ }
+ return py_digest_name(md);
}
static PyGetSetDef EVP_getseters[] = {
@@ -793,21 +798,22 @@ EVPXOF_digest_impl(EVPobject *self, Py_ssize_t length)
}
if (!locked_EVP_MD_CTX_copy(temp_ctx, self)) {
- Py_DECREF(retval);
- EVP_MD_CTX_free(temp_ctx);
- return _setException(PyExc_ValueError, NULL);
+ goto error;
}
if (!EVP_DigestFinalXOF(temp_ctx,
(unsigned char*)PyBytes_AS_STRING(retval),
- length)) {
- Py_DECREF(retval);
- EVP_MD_CTX_free(temp_ctx);
- _setException(PyExc_ValueError, NULL);
- return NULL;
+ length))
+ {
+ goto error;
}
EVP_MD_CTX_free(temp_ctx);
return retval;
+
+error:
+ Py_DECREF(retval);
+ EVP_MD_CTX_free(temp_ctx);
+ return _setException(PyExc_ValueError, NULL);
}
/*[clinic input]
@@ -841,15 +847,10 @@ EVPXOF_hexdigest_impl(EVPobject *self, Py_ssize_t length)
/* Get the raw (binary) digest value */
if (!locked_EVP_MD_CTX_copy(temp_ctx, self)) {
- PyMem_Free(digest);
- EVP_MD_CTX_free(temp_ctx);
- return _setException(PyExc_ValueError, NULL);
+ goto error;
}
if (!EVP_DigestFinalXOF(temp_ctx, digest, length)) {
- PyMem_Free(digest);
- EVP_MD_CTX_free(temp_ctx);
- _setException(PyExc_ValueError, NULL);
- return NULL;
+ goto error;
}
EVP_MD_CTX_free(temp_ctx);
@@ -857,6 +858,11 @@ EVPXOF_hexdigest_impl(EVPobject *self, Py_ssize_t length)
retval = _Py_strhex((const char *)digest, length);
PyMem_Free(digest);
return retval;
+
+error:
+ PyMem_Free(digest);
+ EVP_MD_CTX_free(temp_ctx);
+ return _setException(PyExc_ValueError, NULL);
}
static PyMethodDef EVPXOF_methods[] = {
@@ -957,7 +963,7 @@ py_evp_fromname(PyObject *module, const char *digestname, PyObject *data_obj,
int result = EVP_DigestInit_ex(self->ctx, digest, NULL);
if (!result) {
- _setException(PyExc_ValueError, NULL);
+ (void)_setException(PyExc_ValueError, NULL);
Py_CLEAR(self);
goto exit;
}
@@ -978,7 +984,7 @@ py_evp_fromname(PyObject *module, const char *digestname, PyObject *data_obj,
}
}
- exit:
+exit:
if (data_obj != NULL) {
PyBuffer_Release(&view);
}
@@ -1423,14 +1429,14 @@ _hashlib_scrypt_impl(PyObject *module, Py_buffer *password, Py_buffer *salt,
r = PyLong_AsUnsignedLong(r_obj);
if (r == (unsigned long) -1 && PyErr_Occurred()) {
PyErr_SetString(PyExc_TypeError,
- "r is required and must be an unsigned int");
+ "r is required and must be an unsigned int");
return NULL;
}
p = PyLong_AsUnsignedLong(p_obj);
if (p == (unsigned long) -1 && PyErr_Occurred()) {
PyErr_SetString(PyExc_TypeError,
- "p is required and must be an unsigned int");
+ "p is required and must be an unsigned int");
return NULL;
}
@@ -1439,22 +1445,22 @@ _hashlib_scrypt_impl(PyObject *module, Py_buffer *password, Py_buffer *salt,
future. The maxmem constant is private to OpenSSL. */
PyErr_Format(PyExc_ValueError,
"maxmem must be positive and smaller than %d",
- INT_MAX);
+ INT_MAX);
return NULL;
}
if (dklen < 1 || dklen > INT_MAX) {
PyErr_Format(PyExc_ValueError,
- "dklen must be greater than 0 and smaller than %d",
- INT_MAX);
+ "dklen must be greater than 0 and smaller than %d",
+ INT_MAX);
return NULL;
}
/* let OpenSSL validate the rest */
retval = EVP_PBE_scrypt(NULL, 0, NULL, 0, n, r, p, maxmem, NULL, 0);
if (!retval) {
- _setException(PyExc_ValueError, "Invalid parameter combination for n, r, p, maxmem.");
- return NULL;
+ return _setException(PyExc_ValueError,
+ "Invalid parameter combination for n, r, p, maxmem.");
}
key_obj = PyBytes_FromStringAndSize(NULL, dklen);
@@ -1474,8 +1480,7 @@ _hashlib_scrypt_impl(PyObject *module, Py_buffer *password, Py_buffer *salt,
if (!retval) {
Py_CLEAR(key_obj);
- _setException(PyExc_ValueError, NULL);
- return NULL;
+ return _setException(PyExc_ValueError, NULL);
}
return key_obj;
}
@@ -1531,8 +1536,7 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key,
PY_EVP_MD_free(evp);
if (result == NULL) {
- _setException(PyExc_ValueError, NULL);
- return NULL;
+ return _setException(PyExc_ValueError, NULL);
}
return PyBytes_FromStringAndSize((const char*)md, md_len);
}
@@ -1581,6 +1585,7 @@ _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();
goto error;
}
@@ -1588,7 +1593,7 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj,
r = HMAC_Init_ex(ctx, key->buf, (int)key->len, digest, NULL /* impl */);
PY_EVP_MD_free(digest);
if (r == 0) {
- _setException(PyExc_ValueError, NULL);
+ (void)_setException(PyExc_ValueError, NULL);
goto error;
}
@@ -1626,11 +1631,20 @@ locked_HMAC_CTX_copy(HMAC_CTX *new_ctx_p, HMACobject *self)
return result;
}
+/* returning 0 means that an error occurred and an exception is set */
static unsigned int
_hmac_digest_size(HMACobject *self)
{
- unsigned int digest_size = EVP_MD_size(HMAC_CTX_get_md(self->ctx));
+ const EVP_MD *md = HMAC_CTX_get_md(self->ctx);
+ if (md == NULL) {
+ (void)_setException(PyExc_ValueError, NULL);
+ return 0;
+ }
+ unsigned int digest_size = EVP_MD_size(md);
assert(digest_size <= EVP_MAX_MD_SIZE);
+ if (digest_size == 0) {
+ (void)_setException(PyExc_ValueError, NULL);
+ }
return digest_size;
}
@@ -1658,7 +1672,7 @@ _hmac_update(HMACobject *self, PyObject *obj)
PyBuffer_Release(&view);
if (r == 0) {
- _setException(PyExc_ValueError, NULL);
+ (void)_setException(PyExc_ValueError, NULL);
return 0;
}
return 1;
@@ -1711,7 +1725,11 @@ _hmac_dealloc(HMACobject *self)
static PyObject *
_hmac_repr(HMACobject *self)
{
- PyObject *digest_name = py_digest_name(HMAC_CTX_get_md(self->ctx));
+ const EVP_MD *md = HMAC_CTX_get_md(self->ctx);
+ if (md == NULL) {
+ return _setException(PyExc_ValueError, NULL);
+ }
+ PyObject *digest_name = py_digest_name(md);
if (digest_name == NULL) {
return NULL;
}
@@ -1744,18 +1762,18 @@ _hmac_digest(HMACobject *self, unsigned char *buf, unsigned int len)
{
HMAC_CTX *temp_ctx = HMAC_CTX_new();
if (temp_ctx == NULL) {
- PyErr_NoMemory();
+ (void)PyErr_NoMemory();
return 0;
}
if (!locked_HMAC_CTX_copy(temp_ctx, self)) {
HMAC_CTX_free(temp_ctx);
- _setException(PyExc_ValueError, NULL);
+ (void)_setException(PyExc_ValueError, NULL);
return 0;
}
int r = HMAC_Final(temp_ctx, buf, &len);
HMAC_CTX_free(temp_ctx);
if (r == 0) {
- _setException(PyExc_ValueError, NULL);
+ (void)_setException(PyExc_ValueError, NULL);
return 0;
}
return 1;
@@ -1773,7 +1791,7 @@ _hashlib_HMAC_digest_impl(HMACobject *self)
unsigned char digest[EVP_MAX_MD_SIZE];
unsigned int digest_size = _hmac_digest_size(self);
if (digest_size == 0) {
- return _setException(PyExc_ValueError, NULL);
+ return NULL;
}
int r = _hmac_digest(self, digest, digest_size);
if (r == 0) {
@@ -1798,7 +1816,7 @@ _hashlib_HMAC_hexdigest_impl(HMACobject *self)
unsigned char digest[EVP_MAX_MD_SIZE];
unsigned int digest_size = _hmac_digest_size(self);
if (digest_size == 0) {
- return _setException(PyExc_ValueError, NULL);
+ return NULL;
}
int r = _hmac_digest(self, digest, digest_size);
if (r == 0) {
@@ -1812,7 +1830,7 @@ _hashlib_hmac_get_digest_size(HMACobject *self, void *closure)
{
unsigned int digest_size = _hmac_digest_size(self);
if (digest_size == 0) {
- return _setException(PyExc_ValueError, NULL);
+ return NULL;
}
return PyLong_FromLong(digest_size);
}
@@ -1830,7 +1848,11 @@ _hashlib_hmac_get_block_size(HMACobject *self, void *closure)
static PyObject *
_hashlib_hmac_get_name(HMACobject *self, void *closure)
{
- PyObject *digest_name = py_digest_name(HMAC_CTX_get_md(self->ctx));
+ const EVP_MD *md = HMAC_CTX_get_md(self->ctx);
+ if (md == NULL) {
+ return _setException(PyExc_ValueError, NULL);
+ }
+ PyObject *digest_name = py_digest_name(md);
if (digest_name == NULL) {
return NULL;
}
@@ -1981,7 +2003,7 @@ _hashlib_get_fips_mode_impl(PyObject *module)
// But 0 is also a valid result value.
unsigned long errcode = ERR_peek_last_error();
if (errcode) {
- _setException(PyExc_ValueError, NULL);
+ (void)_setException(PyExc_ValueError, NULL);
return -1;
}
}
1
0

March 3, 2025
https://github.com/python/cpython/commit/d25da899535f752be251bee8453b2c6942…
commit: d25da899535f752be251bee8453b2c6942befaea
branch: 3.12
author: Bénédikt Tran <10796600+picnixz(a)users.noreply.github.com>
committer: picnixz <10796600+picnixz(a)users.noreply.github.com>
date: 2025-03-03T10:44:42Z
summary:
[3.12] gh-127667: fix memory leaks in `hashlib` (GH-127668) (#130783)
gh-127667: fix memory leaks in `hashlib` (GH-127668)
- Correctly handle `NULL` values returned by `EVP_MD_CTX_md`.
- Correctly free resources in error branches.
- Consistently suppress `_setException()` return value when needed.
- Collapse `_setException() + return NULL` into a single statement.
(cherry-picked from commit 097846502b7f33cb327d512e2a396acf4f4de46e)
files:
M Modules/_hashopenssl.c
diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c
index 84afb743ab8c3b..48d7fd03feb16b 100644
--- a/Modules/_hashopenssl.c
+++ b/Modules/_hashopenssl.c
@@ -409,7 +409,8 @@ py_digest_by_name(PyObject *module, const char *name, enum Py_hash_type py_ht)
}
}
if (digest == NULL) {
- _setException(state->unsupported_digestmod_error, "unsupported hash type %s", name);
+ (void)_setException(state->unsupported_digestmod_error,
+ "unsupported hash type %s", name);
return NULL;
}
return digest;
@@ -424,7 +425,6 @@ py_digest_by_name(PyObject *module, const char *name, enum Py_hash_type py_ht)
*/
static PY_EVP_MD*
py_digest_by_digestmod(PyObject *module, PyObject *digestmod, enum Py_hash_type py_ht) {
- PY_EVP_MD* evp;
PyObject *name_obj = NULL;
const char *name;
@@ -450,12 +450,7 @@ py_digest_by_digestmod(PyObject *module, PyObject *digestmod, enum Py_hash_type
return NULL;
}
- evp = py_digest_by_name(module, name, py_ht);
- if (evp == NULL) {
- return NULL;
- }
-
- return evp;
+ return py_digest_by_name(module, name, py_ht);
}
static EVPobject *
@@ -489,7 +484,7 @@ EVP_hash(EVPobject *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)) {
- _setException(PyExc_ValueError, NULL);
+ (void)_setException(PyExc_ValueError, NULL);
return -1;
}
len -= process;
@@ -567,17 +562,20 @@ EVP_digest_impl(EVPobject *self)
}
if (!locked_EVP_MD_CTX_copy(temp_ctx, self)) {
- return _setException(PyExc_ValueError, NULL);
+ goto error;
}
digest_size = EVP_MD_CTX_size(temp_ctx);
if (!EVP_DigestFinal(temp_ctx, digest, NULL)) {
- _setException(PyExc_ValueError, NULL);
- return 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);
+ return _setException(PyExc_ValueError, NULL);
}
/*[clinic input]
@@ -602,17 +600,20 @@ EVP_hexdigest_impl(EVPobject *self)
/* Get the raw (binary) digest value */
if (!locked_EVP_MD_CTX_copy(temp_ctx, self)) {
- return _setException(PyExc_ValueError, NULL);
+ goto error;
}
digest_size = EVP_MD_CTX_size(temp_ctx);
if (!EVP_DigestFinal(temp_ctx, digest, NULL)) {
- _setException(PyExc_ValueError, NULL);
- return 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);
+ return _setException(PyExc_ValueError, NULL);
}
/*[clinic input]
@@ -682,7 +683,11 @@ EVP_get_digest_size(EVPobject *self, void *closure)
static PyObject *
EVP_get_name(EVPobject *self, void *closure)
{
- return py_digest_name(EVP_MD_CTX_md(self->ctx));
+ const EVP_MD *md = EVP_MD_CTX_md(self->ctx);
+ if (md == NULL) {
+ return _setException(PyExc_ValueError, NULL);
+ }
+ return py_digest_name(md);
}
static PyGetSetDef EVP_getseters[] = {
@@ -780,21 +785,22 @@ EVPXOF_digest_impl(EVPobject *self, Py_ssize_t length)
}
if (!locked_EVP_MD_CTX_copy(temp_ctx, self)) {
- Py_DECREF(retval);
- EVP_MD_CTX_free(temp_ctx);
- return _setException(PyExc_ValueError, NULL);
+ goto error;
}
if (!EVP_DigestFinalXOF(temp_ctx,
(unsigned char*)PyBytes_AS_STRING(retval),
- length)) {
- Py_DECREF(retval);
- EVP_MD_CTX_free(temp_ctx);
- _setException(PyExc_ValueError, NULL);
- return NULL;
+ length))
+ {
+ goto error;
}
EVP_MD_CTX_free(temp_ctx);
return retval;
+
+error:
+ Py_DECREF(retval);
+ EVP_MD_CTX_free(temp_ctx);
+ return _setException(PyExc_ValueError, NULL);
}
/*[clinic input]
@@ -828,15 +834,10 @@ EVPXOF_hexdigest_impl(EVPobject *self, Py_ssize_t length)
/* Get the raw (binary) digest value */
if (!locked_EVP_MD_CTX_copy(temp_ctx, self)) {
- PyMem_Free(digest);
- EVP_MD_CTX_free(temp_ctx);
- return _setException(PyExc_ValueError, NULL);
+ goto error;
}
if (!EVP_DigestFinalXOF(temp_ctx, digest, length)) {
- PyMem_Free(digest);
- EVP_MD_CTX_free(temp_ctx);
- _setException(PyExc_ValueError, NULL);
- return NULL;
+ goto error;
}
EVP_MD_CTX_free(temp_ctx);
@@ -844,6 +845,11 @@ EVPXOF_hexdigest_impl(EVPobject *self, Py_ssize_t length)
retval = _Py_strhex((const char *)digest, length);
PyMem_Free(digest);
return retval;
+
+error:
+ PyMem_Free(digest);
+ EVP_MD_CTX_free(temp_ctx);
+ return _setException(PyExc_ValueError, NULL);
}
static PyMethodDef EVPXOF_methods[] = {
@@ -944,7 +950,7 @@ py_evp_fromname(PyObject *module, const char *digestname, PyObject *data_obj,
int result = EVP_DigestInit_ex(self->ctx, digest, NULL);
if (!result) {
- _setException(PyExc_ValueError, NULL);
+ (void)_setException(PyExc_ValueError, NULL);
Py_CLEAR(self);
goto exit;
}
@@ -965,7 +971,7 @@ py_evp_fromname(PyObject *module, const char *digestname, PyObject *data_obj,
}
}
- exit:
+exit:
if (data_obj != NULL) {
PyBuffer_Release(&view);
}
@@ -1410,14 +1416,14 @@ _hashlib_scrypt_impl(PyObject *module, Py_buffer *password, Py_buffer *salt,
r = PyLong_AsUnsignedLong(r_obj);
if (r == (unsigned long) -1 && PyErr_Occurred()) {
PyErr_SetString(PyExc_TypeError,
- "r is required and must be an unsigned int");
+ "r is required and must be an unsigned int");
return NULL;
}
p = PyLong_AsUnsignedLong(p_obj);
if (p == (unsigned long) -1 && PyErr_Occurred()) {
PyErr_SetString(PyExc_TypeError,
- "p is required and must be an unsigned int");
+ "p is required and must be an unsigned int");
return NULL;
}
@@ -1426,22 +1432,22 @@ _hashlib_scrypt_impl(PyObject *module, Py_buffer *password, Py_buffer *salt,
future. The maxmem constant is private to OpenSSL. */
PyErr_Format(PyExc_ValueError,
"maxmem must be positive and smaller than %d",
- INT_MAX);
+ INT_MAX);
return NULL;
}
if (dklen < 1 || dklen > INT_MAX) {
PyErr_Format(PyExc_ValueError,
- "dklen must be greater than 0 and smaller than %d",
- INT_MAX);
+ "dklen must be greater than 0 and smaller than %d",
+ INT_MAX);
return NULL;
}
/* let OpenSSL validate the rest */
retval = EVP_PBE_scrypt(NULL, 0, NULL, 0, n, r, p, maxmem, NULL, 0);
if (!retval) {
- _setException(PyExc_ValueError, "Invalid parameter combination for n, r, p, maxmem.");
- return NULL;
+ return _setException(PyExc_ValueError,
+ "Invalid parameter combination for n, r, p, maxmem.");
}
key_obj = PyBytes_FromStringAndSize(NULL, dklen);
@@ -1461,8 +1467,7 @@ _hashlib_scrypt_impl(PyObject *module, Py_buffer *password, Py_buffer *salt,
if (!retval) {
Py_CLEAR(key_obj);
- _setException(PyExc_ValueError, NULL);
- return NULL;
+ return _setException(PyExc_ValueError, NULL);
}
return key_obj;
}
@@ -1518,8 +1523,7 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key,
PY_EVP_MD_free(evp);
if (result == NULL) {
- _setException(PyExc_ValueError, NULL);
- return NULL;
+ return _setException(PyExc_ValueError, NULL);
}
return PyBytes_FromStringAndSize((const char*)md, md_len);
}
@@ -1568,6 +1572,7 @@ _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();
goto error;
}
@@ -1575,7 +1580,7 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj,
r = HMAC_Init_ex(ctx, key->buf, (int)key->len, digest, NULL /* impl */);
PY_EVP_MD_free(digest);
if (r == 0) {
- _setException(PyExc_ValueError, NULL);
+ (void)_setException(PyExc_ValueError, NULL);
goto error;
}
@@ -1613,11 +1618,20 @@ locked_HMAC_CTX_copy(HMAC_CTX *new_ctx_p, HMACobject *self)
return result;
}
+/* returning 0 means that an error occurred and an exception is set */
static unsigned int
_hmac_digest_size(HMACobject *self)
{
- unsigned int digest_size = EVP_MD_size(HMAC_CTX_get_md(self->ctx));
+ const EVP_MD *md = HMAC_CTX_get_md(self->ctx);
+ if (md == NULL) {
+ (void)_setException(PyExc_ValueError, NULL);
+ return 0;
+ }
+ unsigned int digest_size = EVP_MD_size(md);
assert(digest_size <= EVP_MAX_MD_SIZE);
+ if (digest_size == 0) {
+ (void)_setException(PyExc_ValueError, NULL);
+ }
return digest_size;
}
@@ -1647,7 +1661,7 @@ _hmac_update(HMACobject *self, PyObject *obj)
PyBuffer_Release(&view);
if (r == 0) {
- _setException(PyExc_ValueError, NULL);
+ (void)_setException(PyExc_ValueError, NULL);
return 0;
}
return 1;
@@ -1703,7 +1717,11 @@ _hmac_dealloc(HMACobject *self)
static PyObject *
_hmac_repr(HMACobject *self)
{
- PyObject *digest_name = py_digest_name(HMAC_CTX_get_md(self->ctx));
+ const EVP_MD *md = HMAC_CTX_get_md(self->ctx);
+ if (md == NULL) {
+ return _setException(PyExc_ValueError, NULL);
+ }
+ PyObject *digest_name = py_digest_name(md);
if (digest_name == NULL) {
return NULL;
}
@@ -1736,18 +1754,18 @@ _hmac_digest(HMACobject *self, unsigned char *buf, unsigned int len)
{
HMAC_CTX *temp_ctx = HMAC_CTX_new();
if (temp_ctx == NULL) {
- PyErr_NoMemory();
+ (void)PyErr_NoMemory();
return 0;
}
if (!locked_HMAC_CTX_copy(temp_ctx, self)) {
HMAC_CTX_free(temp_ctx);
- _setException(PyExc_ValueError, NULL);
+ (void)_setException(PyExc_ValueError, NULL);
return 0;
}
int r = HMAC_Final(temp_ctx, buf, &len);
HMAC_CTX_free(temp_ctx);
if (r == 0) {
- _setException(PyExc_ValueError, NULL);
+ (void)_setException(PyExc_ValueError, NULL);
return 0;
}
return 1;
@@ -1765,7 +1783,7 @@ _hashlib_HMAC_digest_impl(HMACobject *self)
unsigned char digest[EVP_MAX_MD_SIZE];
unsigned int digest_size = _hmac_digest_size(self);
if (digest_size == 0) {
- return _setException(PyExc_ValueError, NULL);
+ return NULL;
}
int r = _hmac_digest(self, digest, digest_size);
if (r == 0) {
@@ -1790,7 +1808,7 @@ _hashlib_HMAC_hexdigest_impl(HMACobject *self)
unsigned char digest[EVP_MAX_MD_SIZE];
unsigned int digest_size = _hmac_digest_size(self);
if (digest_size == 0) {
- return _setException(PyExc_ValueError, NULL);
+ return NULL;
}
int r = _hmac_digest(self, digest, digest_size);
if (r == 0) {
@@ -1804,7 +1822,7 @@ _hashlib_hmac_get_digest_size(HMACobject *self, void *closure)
{
unsigned int digest_size = _hmac_digest_size(self);
if (digest_size == 0) {
- return _setException(PyExc_ValueError, NULL);
+ return NULL;
}
return PyLong_FromLong(digest_size);
}
@@ -1822,7 +1840,11 @@ _hashlib_hmac_get_block_size(HMACobject *self, void *closure)
static PyObject *
_hashlib_hmac_get_name(HMACobject *self, void *closure)
{
- PyObject *digest_name = py_digest_name(HMAC_CTX_get_md(self->ctx));
+ const EVP_MD *md = HMAC_CTX_get_md(self->ctx);
+ if (md == NULL) {
+ return _setException(PyExc_ValueError, NULL);
+ }
+ PyObject *digest_name = py_digest_name(md);
if (digest_name == NULL) {
return NULL;
}
@@ -1978,7 +2000,7 @@ _hashlib_get_fips_mode_impl(PyObject *module)
// But 0 is also a valid result value.
unsigned long errcode = ERR_peek_last_error();
if (errcode) {
- _setException(PyExc_ValueError, NULL);
+ (void)_setException(PyExc_ValueError, NULL);
return -1;
}
}
1
0
https://github.com/python/cpython/commit/8f11af45de68459d9d4051812aa5ddaf6a…
commit: 8f11af45de68459d9d4051812aa5ddaf6a98dcb2
branch: main
author: Bénédikt Tran <10796600+picnixz(a)users.noreply.github.com>
committer: picnixz <10796600+picnixz(a)users.noreply.github.com>
date: 2025-03-03T11:22:05+01:00
summary:
gh-130149: refactor tests for HMAC (#130150)
Since we plan to introduce a built-in implementation for HMAC based on HACL*,
it becomes important for the HMAC tests to be flexible enough to avoid code
duplication.
In addition to the new layout based on mixin classes, we extend test coverage by
also testing the `__repr__` of HMAC objects and the HMAC one-shot functions.
We also fix the import to `_sha256` which, since gh-101924, resulted in some tests being
skipped as the module is no more available (its content was moved to the `_sha2` module).
files:
M Lib/test/support/hashlib_helper.py
M Lib/test/test_hmac.py
diff --git a/Lib/test/support/hashlib_helper.py b/Lib/test/support/hashlib_helper.py
index a4e6c92203ab50..477e0f110eabba 100644
--- a/Lib/test/support/hashlib_helper.py
+++ b/Lib/test/support/hashlib_helper.py
@@ -8,6 +8,10 @@
_hashlib = None
+def requires_hashlib():
+ return unittest.skipIf(_hashlib is None, "requires _hashlib")
+
+
def requires_hashdigest(digestname, openssl=None, usedforsecurity=True):
"""Decorator raising SkipTest if a hashing algorithm is not available
@@ -44,7 +48,7 @@ def wrapper(*args, **kwargs):
hashlib.new(digestname, usedforsecurity=usedforsecurity)
except ValueError:
raise unittest.SkipTest(
- f"hash digest '{digestname}' is not available."
+ f"hash digest {digestname!r} is not available."
)
return func_or_class(*args, **kwargs)
return wrapper
diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py
index 1502fba9f3e8b8..982a5c53e0f324 100644
--- a/Lib/test/test_hmac.py
+++ b/Lib/test/test_hmac.py
@@ -2,138 +2,340 @@
import functools
import hmac
import hashlib
+import random
+import test.support.hashlib_helper as hashlib_helper
import unittest
import unittest.mock
import warnings
-
-from test.support import hashlib_helper, check_disallow_instantiation
-
from _operator import _compare_digest as operator_compare_digest
+from test.support import check_disallow_instantiation
+from test.support.import_helper import import_fresh_module
try:
- import _hashlib as _hashopenssl
- from _hashlib import HMAC as C_HMAC
- from _hashlib import hmac_new as c_hmac_new
+ import _hashlib
from _hashlib import compare_digest as openssl_compare_digest
except ImportError:
- _hashopenssl = None
- C_HMAC = None
- c_hmac_new = None
+ _hashlib = None
openssl_compare_digest = None
try:
- import _sha256 as sha256_module
+ import _sha2 as sha2
except ImportError:
- sha256_module = None
+ sha2 = None
-def ignore_warning(func):
- @functools.wraps(func)
- def wrapper(*args, **kwargs):
- with warnings.catch_warnings():
- warnings.filterwarnings("ignore",
- category=DeprecationWarning)
- return func(*args, **kwargs)
- return wrapper
+def requires_builtin_sha2():
+ return unittest.skipIf(sha2 is None, "requires _sha2")
-class TestVectorsTestCase(unittest.TestCase):
+class ModuleMixin:
+ """Mixin with a HMAC module implementation."""
- def assert_hmac_internals(
- self, h, digest, hashname, digest_size, block_size
- ):
- self.assertEqual(h.hexdigest().upper(), digest.upper())
- self.assertEqual(h.digest(), binascii.unhexlify(digest))
+ hmac = None
+
+
+class PyModuleMixin(ModuleMixin):
+ """Pure Python implementation of HMAC.
+
+ The underlying hash functions may be OpenSSL-based or HACL* based,
+ depending on whether OpenSSL is present or not.
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.hmac = import_fresh_module('hmac', blocked=['_hashlib', '_hmac'])
+
+
+(a)unittest.skip("no builtin implementation for HMAC for now")
+class BuiltinModuleMixin(ModuleMixin):
+ """Built-in HACL* implementation of HMAC."""
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.hmac = import_fresh_module('_hmac')
+
+
+class CreatorMixin:
+ """Mixin exposing a method creating a HMAC object."""
+
+ def hmac_new(self, key, msg=None, digestmod=None):
+ """Create a new HMAC object."""
+ raise NotImplementedError
+
+ def bind_hmac_new(self, digestmod):
+ """Return a specialization of hmac_new() with a bound digestmod."""
+ return functools.partial(self.hmac_new, digestmod=digestmod)
+
+
+class DigestMixin:
+ """Mixin exposing a method computing a HMAC digest."""
+
+ def hmac_digest(self, key, msg=None, digestmod=None):
+ """Compute a HMAC digest."""
+ raise NotImplementedError
+
+ def bind_hmac_digest(self, digestmod):
+ """Return a specialization of hmac_digest() with a bound digestmod."""
+ return functools.partial(self.hmac_digest, digestmod=digestmod)
+
+
+class ThroughObjectMixin(ModuleMixin, CreatorMixin, DigestMixin):
+ """Mixin delegating to <module>.HMAC() and <module>.HMAC(...).digest().
+
+ Both the C implementation and the Python implementation of HMAC should
+ expose a HMAC class with the same functionalities.
+ """
+
+ def hmac_new(self, key, msg=None, digestmod=None):
+ """Create a HMAC object via a module-level class constructor."""
+ return self.hmac.HMAC(key, msg, digestmod=digestmod)
+
+ def hmac_digest(self, key, msg=None, digestmod=None):
+ """Call the digest() method on a HMAC object obtained by hmac_new()."""
+ return self.hmac_new(key, msg, digestmod).digest()
+
+
+class ThroughModuleAPIMixin(ModuleMixin, CreatorMixin, DigestMixin):
+ """Mixin delegating to <module>.new() and <module>.digest()."""
+
+ def hmac_new(self, key, msg=None, digestmod=None):
+ """Create a HMAC object via a module-level function."""
+ return self.hmac.new(key, msg, digestmod=digestmod)
+
+ def hmac_digest(self, key, msg=None, digestmod=None):
+ """One-shot HMAC digest computation."""
+ return self.hmac.digest(key, msg, digest=digestmod)
+
+
+(a)hashlib_helper.requires_hashlib()
+class ThroughOpenSSLAPIMixin(CreatorMixin, DigestMixin):
+ """Mixin delegating to _hashlib.hmac_new() and _hashlib.hmac_digest()."""
+
+ def hmac_new(self, key, msg=None, digestmod=None):
+ return _hashlib.hmac_new(key, msg, digestmod=digestmod)
+
+ def hmac_digest(self, key, msg=None, digestmod=None):
+ return _hashlib.hmac_digest(key, msg, digest=digestmod)
+
+
+class CheckerMixin:
+ """Mixin for checking HMAC objects (pure Python, OpenSSL or built-in)."""
+
+ def check_object(self, h, hexdigest, hashname, digest_size, block_size):
+ """Check a HMAC object 'h' against the given values."""
+ self.check_internals(h, hashname, digest_size, block_size)
+ self.check_hexdigest(h, hexdigest, digest_size)
+
+ def check_internals(self, h, hashname, digest_size, block_size):
+ """Check the constant attributes of a HMAC object."""
self.assertEqual(h.name, f"hmac-{hashname}")
self.assertEqual(h.digest_size, digest_size)
self.assertEqual(h.block_size, block_size)
+ def check_hexdigest(self, h, hexdigest, digest_size):
+ """Check the HMAC digest of 'h' and its size."""
+ self.assertEqual(len(h.digest()), digest_size)
+ self.assertEqual(h.digest(), binascii.unhexlify(hexdigest))
+ self.assertEqual(h.hexdigest().upper(), hexdigest.upper())
+
+
+class TestVectorsMixin(CreatorMixin, DigestMixin, CheckerMixin):
+ """Mixin class for all test vectors test cases."""
+
+ def hmac_new_by_name(self, key, msg=None, hashname=None):
+ """Alternative implementation of hmac_new().
+
+ This is typically useful when one needs to test against an HMAC
+ implementation which only recognizes underlying hash functions
+ by their name (all HMAC implementations must at least recognize
+ hash functions by their names but some may use aliases such as
+ `hashlib.sha1` instead of "sha1").
+ """
+ self.assertIsInstance(hashname, str | None)
+ return self.hmac_new(key, msg, digestmod=hashname)
+
+ def hmac_digest_by_name(self, key, msg=None, hashname=None):
+ """Alternative implementation of hmac_digest()."""
+ self.assertIsInstance(hashname, str | None)
+ return self.hmac_digest(key, msg, digestmod=hashname)
+
def assert_hmac(
- self, key, data, digest, hashfunc, hashname, digest_size, block_size
+ self, key, msg, hexdigest, hashfunc, hashname, digest_size, block_size
):
- h = hmac.HMAC(key, data, digestmod=hashfunc)
- self.assert_hmac_internals(
- h, digest, hashname, digest_size, block_size
+ """Check that HMAC(key, msg) == digest.
+
+ The 'hashfunc' and 'hashname' are used as 'digestmod' values,
+ thereby allowing to test the underlying dispatching mechanism.
+
+ Note that 'hashfunc' may be a string, a callable, or a PEP-257
+ module. Note that not all HMAC implementations may recognize the
+ same set of types for 'hashfunc', but they should always accept
+ a hash function by its name.
+ """
+ if hashfunc == hashname:
+ choices = [hashname]
+ else:
+ choices = [hashfunc, hashname]
+
+ for digestmod in choices:
+ with self.subTest(digestmod=digestmod):
+ self.assert_hmac_new(
+ key, msg, hexdigest, digestmod,
+ hashname, digest_size, block_size
+ )
+ self.assert_hmac_hexdigest(
+ key, msg, hexdigest, digestmod, digest_size
+ )
+ self.assert_hmac_extra_cases(
+ key, msg, hexdigest, digestmod,
+ hashname, digest_size, block_size
+ )
+
+ self.assert_hmac_new_by_name(
+ key, msg, hexdigest, hashname, digest_size, block_size
)
-
- h = hmac.HMAC(key, data, digestmod=hashname)
- self.assert_hmac_internals(
- h, digest, hashname, digest_size, block_size
+ self.assert_hmac_hexdigest_by_new(
+ key, msg, hexdigest, hashname, digest_size
)
- h = hmac.HMAC(key, digestmod=hashname)
- h2 = h.copy()
- h2.update(b"test update")
- h.update(data)
- self.assertEqual(h.hexdigest().upper(), digest.upper())
+ def assert_hmac_new(
+ self, key, msg, hexdigest, digestmod, hashname, digest_size, block_size
+ ):
+ """Check that HMAC(key, msg) == digest.
+
+ This test uses the `hmac_new()` method to create HMAC objects.
+ """
+ self._check_hmac_new(
+ key, msg, hexdigest, hashname, digest_size, block_size,
+ hmac_new_func=self.hmac_new,
+ hmac_new_kwds={'digestmod': digestmod},
+ )
- h = hmac.new(key, data, digestmod=hashname)
- self.assert_hmac_internals(
- h, digest, hashname, digest_size, block_size
+ def assert_hmac_new_by_name(
+ self, key, msg, hexdigest, hashname, digest_size, block_size
+ ):
+ """Check that HMAC(key, msg) == digest.
+
+ This test uses the `hmac_new_by_name()` method to create HMAC objects.
+ """
+ self._check_hmac_new(
+ key, msg, hexdigest, hashname, digest_size, block_size,
+ hmac_new_func=self.hmac_new_by_name,
+ hmac_new_kwds={'hashname': hashname},
)
- h = hmac.new(key, None, digestmod=hashname)
- h.update(data)
- self.assertEqual(h.hexdigest().upper(), digest.upper())
+ def _check_hmac_new(
+ self, key, msg, hexdigest, hashname, digest_size, block_size,
+ hmac_new_func, hmac_new_kwds,
+ ):
+ """Check that HMAC(key, msg) == digest.
+
+ This also tests that using an empty/None initial message and
+ then calling `h.update(msg)` produces the same result, namely
+ that HMAC(key, msg) is equivalent to HMAC(key).update(msg).
+ """
+ h = hmac_new_func(key, msg, **hmac_new_kwds)
+ self.check_object(h, hexdigest, hashname, digest_size, block_size)
+
+ def hmac_new_feed(*args):
+ h = hmac_new_func(key, *args, **hmac_new_kwds)
+ h.update(msg)
+ self.check_hexdigest(h, hexdigest, digest_size)
+
+ with self.subTest('no initial message'):
+ hmac_new_feed()
+ with self.subTest('initial message is empty'):
+ hmac_new_feed(b'')
+ with self.subTest('initial message is None'):
+ hmac_new_feed(None)
+
+ def assert_hmac_hexdigest(
+ self, key, msg, hexdigest, digestmod, digest_size,
+ ):
+ """Check a HMAC digest computed by hmac_digest()."""
+ d = self.hmac_digest(key, msg, digestmod=digestmod)
+ self.assertEqual(len(d), digest_size)
+ self.assertEqual(d, binascii.unhexlify(hexdigest))
+
+ def assert_hmac_hexdigest_by_new(
+ self, key, msg, hexdigest, hashname, digest_size
+ ):
+ """Check a HMAC digest computed by hmac_digest_by_name()."""
+ self.assertIsInstance(hashname, str | None)
+ d = self.hmac_digest_by_name(key, msg, hashname=hashname)
+ self.assertEqual(len(d), digest_size)
+ self.assertEqual(d, binascii.unhexlify(hexdigest))
+
+ def assert_hmac_extra_cases(
+ self, key, msg, hexdigest, digestmod, hashname, digest_size, block_size
+ ):
+ """Extra tests that can be added in subclasses."""
+ h1 = self.hmac_new_by_name(key, hashname=hashname)
+ h2 = h1.copy()
+ h2.update(b"test update should not affect original")
+ h1.update(msg)
+ self.check_object(h1, hexdigest, hashname, digest_size, block_size)
- h = hmac.new(key, digestmod=hashname)
- h.update(data)
- self.assertEqual(h.hexdigest().upper(), digest.upper())
- h = hmac.new(key, data, digestmod=hashfunc)
- self.assertEqual(h.hexdigest().upper(), digest.upper())
+class PyTestVectorsMixin(PyModuleMixin, TestVectorsMixin):
- self.assertEqual(
- hmac.digest(key, data, digest=hashname),
- binascii.unhexlify(digest)
- )
- self.assertEqual(
- hmac.digest(key, data, digest=hashfunc),
- binascii.unhexlify(digest)
+ def assert_hmac_extra_cases(
+ self, key, msg, hexdigest, digestmod, hashname, digest_size, block_size
+ ):
+ super().assert_hmac_extra_cases(
+ key, msg, hexdigest, digestmod, hashname, digest_size, block_size
)
- h = hmac.HMAC.__new__(hmac.HMAC)
- h._init_old(key, data, digestmod=hashname)
- self.assert_hmac_internals(
- h, digest, hashname, digest_size, block_size
- )
+ h = self.hmac.HMAC.__new__(self.hmac.HMAC)
+ h._init_old(key, msg, digestmod=digestmod)
+ self.check_object(h, hexdigest, hashname, digest_size, block_size)
- if c_hmac_new is not None:
- h = c_hmac_new(key, data, digestmod=hashname)
- self.assert_hmac_internals(
- h, digest, hashname, digest_size, block_size
- )
- h = c_hmac_new(key, digestmod=hashname)
- h2 = h.copy()
- h2.update(b"test update")
- h.update(data)
- self.assertEqual(h.hexdigest().upper(), digest.upper())
+class OpenSSLTestVectorsMixin(TestVectorsMixin):
- func = getattr(_hashopenssl, f"openssl_{hashname}")
- h = c_hmac_new(key, data, digestmod=func)
- self.assert_hmac_internals(
- h, digest, hashname, digest_size, block_size
- )
+ def hmac_new(self, key, msg=None, digestmod=None):
+ return _hashlib.hmac_new(key, msg, digestmod=digestmod)
- h = hmac.HMAC.__new__(hmac.HMAC)
- h._init_hmac(key, data, digestmod=hashname)
- self.assert_hmac_internals(
- h, digest, hashname, digest_size, block_size
- )
+ def hmac_digest(self, key, msg=None, digestmod=None):
+ return _hashlib.hmac_digest(key, msg, digest=digestmod)
- @hashlib_helper.requires_hashdigest('md5', openssl=True)
- def test_md5_vectors(self):
- # Test the HMAC module against test vectors from the RFC.
+ def hmac_new_by_name(self, key, msg=None, hashname=None):
+ # ignore 'digestmod' and use the exact openssl function
+ openssl_func = getattr(_hashlib, f"openssl_{hashname}")
+ return self.hmac_new(key, msg, digestmod=openssl_func)
- def md5test(key, data, digest):
- self.assert_hmac(
- key, data, digest,
- hashfunc=hashlib.md5,
- hashname="md5",
- digest_size=16,
- block_size=64
- )
+ def hmac_digest_by_name(self, key, msg=None, hashname=None):
+ openssl_func = getattr(_hashlib, f"openssl_{hashname}")
+ return self.hmac_digest(key, msg, digestmod=openssl_func)
+
+
+class RFCTestCasesMixin(TestVectorsMixin):
+ """Test HMAC implementations against test vectors from the RFC.
+
+ Subclasses must override the 'md5' and other 'sha*' attributes
+ to test the implementations. Their value can be a string, a callable,
+ or a PEP-257 module.
+ """
+
+ ALGORITHMS = [
+ 'md5', 'sha1',
+ 'sha224', 'sha256', 'sha384', 'sha512',
+ ]
+
+ # Those will be automatically set to non-None on subclasses
+ # as they are set by __init_subclass()__.
+ md5 = sha1 = sha224 = sha256 = sha384 = sha512 = None
+
+ def __init_subclass__(cls, *args, **kwargs):
+ super().__init_subclass__(*args, **kwargs)
+ for name in cls.ALGORITHMS:
+ setattr(cls, name, name)
+
+ def test_md5(self):
+ def md5test(key, msg, hexdigest):
+ self.assert_hmac(key, msg, hexdigest, self.md5, "md5", 16, 64)
md5test(b"\x0b" * 16,
b"Hi There",
@@ -164,16 +366,9 @@ def md5test(key, data, digest):
b"and Larger Than One Block-Size Data"),
"6f630fad67cda0ee1fb1f562db3aa53e")
- @hashlib_helper.requires_hashdigest('sha1', openssl=True)
- def test_sha_vectors(self):
- def shatest(key, data, digest):
- self.assert_hmac(
- key, data, digest,
- hashfunc=hashlib.sha1,
- hashname="sha1",
- digest_size=20,
- block_size=64
- )
+ def test_sha1(self):
+ def shatest(key, msg, hexdigest):
+ self.assert_hmac(key, msg, hexdigest, self.sha1, "sha1", 20, 64)
shatest(b"\x0b" * 20,
b"Hi There",
@@ -204,141 +399,138 @@ def shatest(key, data, digest):
b"and Larger Than One Block-Size Data"),
"e8e99d0f45237d786d6bbaa7965c7808bbff1a91")
- def _rfc4231_test_cases(self, hashfunc, hash_name, digest_size, block_size):
+ def test_sha2_224_rfc4231(self):
+ self._test_sha2_rfc4231(self.sha224, 'sha224', 28, 64)
+
+ def test_sha2_256_rfc4231(self):
+ self._test_sha2_rfc4231(self.sha256, 'sha256', 32, 64)
+
+ def test_sha2_384_rfc4231(self):
+ self._test_sha2_rfc4231(self.sha384, 'sha384', 48, 128)
+
+ def test_sha2_512_rfc4231(self):
+ self._test_sha2_rfc4231(self.sha512, 'sha512', 64, 128)
+
+ def _test_sha2_rfc4231(self, hashfunc, hashname, digest_size, block_size):
+
def hmactest(key, data, hexdigests):
- digest = hexdigests[hashfunc]
+ hexdigest = hexdigests[hashname]
self.assert_hmac(
- key, data, digest,
+ key, data, hexdigest,
hashfunc=hashfunc,
- hashname=hash_name,
+ hashname=hashname,
digest_size=digest_size,
block_size=block_size
)
# 4.2. Test Case 1
- hmactest(key = b'\x0b'*20,
- data = b'Hi There',
- hexdigests = {
- hashlib.sha224: '896fb1128abbdf196832107cd49df33f'
- '47b4b1169912ba4f53684b22',
- hashlib.sha256: 'b0344c61d8db38535ca8afceaf0bf12b'
- '881dc200c9833da726e9376c2e32cff7',
- hashlib.sha384: 'afd03944d84895626b0825f4ab46907f'
- '15f9dadbe4101ec682aa034c7cebc59c'
- 'faea9ea9076ede7f4af152e8b2fa9cb6',
- hashlib.sha512: '87aa7cdea5ef619d4ff0b4241a1d6cb0'
- '2379f4e2ce4ec2787ad0b30545e17cde'
- 'daa833b7d6b8a702038b274eaea3f4e4'
- 'be9d914eeb61f1702e696c203a126854',
+ hmactest(key=b'\x0b' * 20,
+ data=b'Hi There',
+ hexdigests={
+ 'sha224': '896fb1128abbdf196832107cd49df33f'
+ '47b4b1169912ba4f53684b22',
+ 'sha256': 'b0344c61d8db38535ca8afceaf0bf12b'
+ '881dc200c9833da726e9376c2e32cff7',
+ 'sha384': 'afd03944d84895626b0825f4ab46907f'
+ '15f9dadbe4101ec682aa034c7cebc59c'
+ 'faea9ea9076ede7f4af152e8b2fa9cb6',
+ 'sha512': '87aa7cdea5ef619d4ff0b4241a1d6cb0'
+ '2379f4e2ce4ec2787ad0b30545e17cde'
+ 'daa833b7d6b8a702038b274eaea3f4e4'
+ 'be9d914eeb61f1702e696c203a126854',
})
# 4.3. Test Case 2
- hmactest(key = b'Jefe',
- data = b'what do ya want for nothing?',
- hexdigests = {
- hashlib.sha224: 'a30e01098bc6dbbf45690f3a7e9e6d0f'
- '8bbea2a39e6148008fd05e44',
- hashlib.sha256: '5bdcc146bf60754e6a042426089575c7'
- '5a003f089d2739839dec58b964ec3843',
- hashlib.sha384: 'af45d2e376484031617f78d2b58a6b1b'
- '9c7ef464f5a01b47e42ec3736322445e'
- '8e2240ca5e69e2c78b3239ecfab21649',
- hashlib.sha512: '164b7a7bfcf819e2e395fbe73b56e0a3'
- '87bd64222e831fd610270cd7ea250554'
- '9758bf75c05a994a6d034f65f8f0e6fd'
- 'caeab1a34d4a6b4b636e070a38bce737',
+ hmactest(key=b'Jefe',
+ data=b'what do ya want for nothing?',
+ hexdigests={
+ 'sha224': 'a30e01098bc6dbbf45690f3a7e9e6d0f'
+ '8bbea2a39e6148008fd05e44',
+ 'sha256': '5bdcc146bf60754e6a042426089575c7'
+ '5a003f089d2739839dec58b964ec3843',
+ 'sha384': 'af45d2e376484031617f78d2b58a6b1b'
+ '9c7ef464f5a01b47e42ec3736322445e'
+ '8e2240ca5e69e2c78b3239ecfab21649',
+ 'sha512': '164b7a7bfcf819e2e395fbe73b56e0a3'
+ '87bd64222e831fd610270cd7ea250554'
+ '9758bf75c05a994a6d034f65f8f0e6fd'
+ 'caeab1a34d4a6b4b636e070a38bce737',
})
# 4.4. Test Case 3
- hmactest(key = b'\xaa'*20,
- data = b'\xdd'*50,
- hexdigests = {
- hashlib.sha224: '7fb3cb3588c6c1f6ffa9694d7d6ad264'
- '9365b0c1f65d69d1ec8333ea',
- hashlib.sha256: '773ea91e36800e46854db8ebd09181a7'
- '2959098b3ef8c122d9635514ced565fe',
- hashlib.sha384: '88062608d3e6ad8a0aa2ace014c8a86f'
- '0aa635d947ac9febe83ef4e55966144b'
- '2a5ab39dc13814b94e3ab6e101a34f27',
- hashlib.sha512: 'fa73b0089d56a284efb0f0756c890be9'
- 'b1b5dbdd8ee81a3655f83e33b2279d39'
- 'bf3e848279a722c806b485a47e67c807'
- 'b946a337bee8942674278859e13292fb',
+ hmactest(key=b'\xaa' * 20,
+ data=b'\xdd' * 50,
+ hexdigests={
+ 'sha224': '7fb3cb3588c6c1f6ffa9694d7d6ad264'
+ '9365b0c1f65d69d1ec8333ea',
+ 'sha256': '773ea91e36800e46854db8ebd09181a7'
+ '2959098b3ef8c122d9635514ced565fe',
+ 'sha384': '88062608d3e6ad8a0aa2ace014c8a86f'
+ '0aa635d947ac9febe83ef4e55966144b'
+ '2a5ab39dc13814b94e3ab6e101a34f27',
+ 'sha512': 'fa73b0089d56a284efb0f0756c890be9'
+ 'b1b5dbdd8ee81a3655f83e33b2279d39'
+ 'bf3e848279a722c806b485a47e67c807'
+ 'b946a337bee8942674278859e13292fb',
})
# 4.5. Test Case 4
- hmactest(key = bytes(x for x in range(0x01, 0x19+1)),
- data = b'\xcd'*50,
- hexdigests = {
- hashlib.sha224: '6c11506874013cac6a2abc1bb382627c'
- 'ec6a90d86efc012de7afec5a',
- hashlib.sha256: '82558a389a443c0ea4cc819899f2083a'
- '85f0faa3e578f8077a2e3ff46729665b',
- hashlib.sha384: '3e8a69b7783c25851933ab6290af6ca7'
- '7a9981480850009cc5577c6e1f573b4e'
- '6801dd23c4a7d679ccf8a386c674cffb',
- hashlib.sha512: 'b0ba465637458c6990e5a8c5f61d4af7'
- 'e576d97ff94b872de76f8050361ee3db'
- 'a91ca5c11aa25eb4d679275cc5788063'
- 'a5f19741120c4f2de2adebeb10a298dd',
+ hmactest(key=bytes(x for x in range(0x01, 0x19 + 1)),
+ data=b'\xcd' * 50,
+ hexdigests={
+ 'sha224': '6c11506874013cac6a2abc1bb382627c'
+ 'ec6a90d86efc012de7afec5a',
+ 'sha256': '82558a389a443c0ea4cc819899f2083a'
+ '85f0faa3e578f8077a2e3ff46729665b',
+ 'sha384': '3e8a69b7783c25851933ab6290af6ca7'
+ '7a9981480850009cc5577c6e1f573b4e'
+ '6801dd23c4a7d679ccf8a386c674cffb',
+ 'sha512': 'b0ba465637458c6990e5a8c5f61d4af7'
+ 'e576d97ff94b872de76f8050361ee3db'
+ 'a91ca5c11aa25eb4d679275cc5788063'
+ 'a5f19741120c4f2de2adebeb10a298dd',
})
# 4.7. Test Case 6
- hmactest(key = b'\xaa'*131,
- data = b'Test Using Larger Than Block-Siz'
- b'e Key - Hash Key First',
- hexdigests = {
- hashlib.sha224: '95e9a0db962095adaebe9b2d6f0dbce2'
- 'd499f112f2d2b7273fa6870e',
- hashlib.sha256: '60e431591ee0b67f0d8a26aacbf5b77f'
- '8e0bc6213728c5140546040f0ee37f54',
- hashlib.sha384: '4ece084485813e9088d2c63a041bc5b4'
- '4f9ef1012a2b588f3cd11f05033ac4c6'
- '0c2ef6ab4030fe8296248df163f44952',
- hashlib.sha512: '80b24263c7c1a3ebb71493c1dd7be8b4'
- '9b46d1f41b4aeec1121b013783f8f352'
- '6b56d037e05f2598bd0fd2215d6a1e52'
- '95e64f73f63f0aec8b915a985d786598',
+ hmactest(key=b'\xaa' * 131,
+ data=b'Test Using Larger Than Block-Siz'
+ b'e Key - Hash Key First',
+ hexdigests={
+ 'sha224': '95e9a0db962095adaebe9b2d6f0dbce2'
+ 'd499f112f2d2b7273fa6870e',
+ 'sha256': '60e431591ee0b67f0d8a26aacbf5b77f'
+ '8e0bc6213728c5140546040f0ee37f54',
+ 'sha384': '4ece084485813e9088d2c63a041bc5b4'
+ '4f9ef1012a2b588f3cd11f05033ac4c6'
+ '0c2ef6ab4030fe8296248df163f44952',
+ 'sha512': '80b24263c7c1a3ebb71493c1dd7be8b4'
+ '9b46d1f41b4aeec1121b013783f8f352'
+ '6b56d037e05f2598bd0fd2215d6a1e52'
+ '95e64f73f63f0aec8b915a985d786598',
})
# 4.8. Test Case 7
- hmactest(key = b'\xaa'*131,
- data = b'This is a test using a larger th'
- b'an block-size key and a larger t'
- b'han block-size data. The key nee'
- b'ds to be hashed before being use'
- b'd by the HMAC algorithm.',
- hexdigests = {
- hashlib.sha224: '3a854166ac5d9f023f54d517d0b39dbd'
- '946770db9c2b95c9f6f565d1',
- hashlib.sha256: '9b09ffa71b942fcb27635fbcd5b0e944'
- 'bfdc63644f0713938a7f51535c3a35e2',
- hashlib.sha384: '6617178e941f020d351e2f254e8fd32c'
- '602420feb0b8fb9adccebb82461e99c5'
- 'a678cc31e799176d3860e6110c46523e',
- hashlib.sha512: 'e37b6a775dc87dbaa4dfa9f96e5e3ffd'
- 'debd71f8867289865df5a32d20cdc944'
- 'b6022cac3c4982b10d5eeb55c3e4de15'
- '134676fb6de0446065c97440fa8c6a58',
+ hmactest(key=b'\xaa' * 131,
+ data=b'This is a test using a larger th'
+ b'an block-size key and a larger t'
+ b'han block-size data. The key nee'
+ b'ds to be hashed before being use'
+ b'd by the HMAC algorithm.',
+ hexdigests={
+ 'sha224': '3a854166ac5d9f023f54d517d0b39dbd'
+ '946770db9c2b95c9f6f565d1',
+ 'sha256': '9b09ffa71b942fcb27635fbcd5b0e944'
+ 'bfdc63644f0713938a7f51535c3a35e2',
+ 'sha384': '6617178e941f020d351e2f254e8fd32c'
+ '602420feb0b8fb9adccebb82461e99c5'
+ 'a678cc31e799176d3860e6110c46523e',
+ 'sha512': 'e37b6a775dc87dbaa4dfa9f96e5e3ffd'
+ 'debd71f8867289865df5a32d20cdc944'
+ 'b6022cac3c4982b10d5eeb55c3e4de15'
+ '134676fb6de0446065c97440fa8c6a58',
})
- @hashlib_helper.requires_hashdigest('sha224', openssl=True)
- def test_sha224_rfc4231(self):
- self._rfc4231_test_cases(hashlib.sha224, 'sha224', 28, 64)
-
- @hashlib_helper.requires_hashdigest('sha256', openssl=True)
- def test_sha256_rfc4231(self):
- self._rfc4231_test_cases(hashlib.sha256, 'sha256', 32, 64)
-
- @hashlib_helper.requires_hashdigest('sha384', openssl=True)
- def test_sha384_rfc4231(self):
- self._rfc4231_test_cases(hashlib.sha384, 'sha384', 48, 128)
-
- @hashlib_helper.requires_hashdigest('sha512', openssl=True)
- def test_sha512_rfc4231(self):
- self._rfc4231_test_cases(hashlib.sha512, 'sha512', 64, 128)
-
@hashlib_helper.requires_hashdigest('sha256')
def test_legacy_block_size_warnings(self):
class MockCrazyHash(object):
@@ -362,312 +554,517 @@ def digest(self):
hmac.HMAC(b'a', b'b', digestmod=MockCrazyHash)
self.fail('Expected warning about small block_size')
- def test_with_digestmod_no_default(self):
- """The digestmod parameter is required as of Python 3.8."""
- with self.assertRaisesRegex(TypeError, r'required.*digestmod'):
- key = b"\x0b" * 16
- data = b"Hi There"
- hmac.HMAC(key, data, digestmod=None)
- with self.assertRaisesRegex(TypeError, r'required.*digestmod'):
- hmac.new(key, data)
- with self.assertRaisesRegex(TypeError, r'required.*digestmod'):
- hmac.HMAC(key, msg=data, digestmod='')
-
def test_with_fallback(self):
cache = getattr(hashlib, '__builtin_constructor_cache')
try:
cache['foo'] = hashlib.sha256
hexdigest = hmac.digest(b'key', b'message', 'foo').hex()
- expected = '6e9ef29b75fffc5b7abae527d58fdadb2fe42e7219011976917343065f58ed4a'
+ expected = ('6e9ef29b75fffc5b7abae527d58fdadb'
+ '2fe42e7219011976917343065f58ed4a')
self.assertEqual(hexdigest, expected)
finally:
cache.pop('foo')
-class ConstructorTestCase(unittest.TestCase):
+class PyRFCTestCase(PyTestVectorsMixin, ThroughObjectMixin,
+ RFCTestCasesMixin, unittest.TestCase):
+ """Python implementation of HMAC using hmac.HMAC()."""
- expected = (
- "6c845b47f52b3b47f6590c502db7825aad757bf4fadc8fa972f7cd2e76a5bdeb"
- )
- @hashlib_helper.requires_hashdigest('sha256')
- def test_normal(self):
- # Standard constructor call.
- try:
- hmac.HMAC(b"key", digestmod='sha256')
- except Exception:
- self.fail("Standard constructor call raised exception.")
+class PyDotNewRFCTestCase(PyTestVectorsMixin, ThroughModuleAPIMixin,
+ RFCTestCasesMixin, unittest.TestCase):
+ """Python implementation of HMAC using hmac.new()."""
- @hashlib_helper.requires_hashdigest('sha256')
- def test_with_str_key(self):
- # Pass a key of type str, which is an error, because it expects a key
- # of type bytes
- with self.assertRaises(TypeError):
- h = hmac.HMAC("key", digestmod='sha256')
- @hashlib_helper.requires_hashdigest('sha256')
- def test_dot_new_with_str_key(self):
- # Pass a key of type str, which is an error, because it expects a key
- # of type bytes
- with self.assertRaises(TypeError):
- h = hmac.new("key", digestmod='sha256')
+(a)hashlib_helper.requires_hashlib()
+class OpenSSLRFCTestCase(OpenSSLTestVectorsMixin, RFCTestCasesMixin,
+ unittest.TestCase):
+ """OpenSSL implementation of HMAC."""
- @hashlib_helper.requires_hashdigest('sha256')
- def test_withtext(self):
- # Constructor call with text.
- try:
- h = hmac.HMAC(b"key", b"hash this!", digestmod='sha256')
- except Exception:
- self.fail("Constructor call with text argument raised exception.")
- self.assertEqual(h.hexdigest(), self.expected)
+ def __init_subclass__(cls, *args, **kwargs):
+ super().__init_subclass__(*args, **kwargs)
- @hashlib_helper.requires_hashdigest('sha256')
- def test_with_bytearray(self):
- try:
- h = hmac.HMAC(bytearray(b"key"), bytearray(b"hash this!"),
- digestmod="sha256")
- except Exception:
- self.fail("Constructor call with bytearray arguments raised exception.")
- self.assertEqual(h.hexdigest(), self.expected)
+ for name in cls.ALGORITHMS:
+ @property
+ @hashlib_helper.requires_hashdigest(name, openssl=True)
+ def func(self, *, __name=name): # __name needed to bind 'name'
+ return getattr(_hashlib, f'openssl_{__name}')
+ setattr(cls, name, func)
- @hashlib_helper.requires_hashdigest('sha256')
- def test_with_memoryview_msg(self):
- try:
- h = hmac.HMAC(b"key", memoryview(b"hash this!"), digestmod="sha256")
- except Exception:
- self.fail("Constructor call with memoryview msg raised exception.")
- self.assertEqual(h.hexdigest(), self.expected)
- @hashlib_helper.requires_hashdigest('sha256')
- def test_withmodule(self):
- # Constructor call with text and digest module.
- try:
- h = hmac.HMAC(b"key", b"", hashlib.sha256)
- except Exception:
- self.fail("Constructor call with hashlib.sha256 raised exception.")
+class DigestModTestCaseMixin(CreatorMixin, DigestMixin):
+ """Tests for the 'digestmod' parameter."""
+
+ def assert_raises_missing_digestmod(self):
+ """A context manager catching errors when a digestmod is missing."""
+ return self.assertRaisesRegex(TypeError, "Missing required.*digestmod")
+
+ def assert_raises_unknown_digestmod(self):
+ """A context manager catching errors when a digestmod is unknown."""
+ return self.assertRaisesRegex(ValueError, "[Uu]nsupported.*")
+
+ def test_constructor_missing_digestmod(self):
+ catcher = self.assert_raises_missing_digestmod
+ self.do_test_constructor_missing_digestmod(catcher)
+
+ def test_constructor_unknown_digestmod(self):
+ catcher = self.assert_raises_unknown_digestmod
+ self.do_test_constructor_unknown_digestmod(catcher)
+
+ def do_test_constructor_missing_digestmod(self, catcher):
+ for func, args, kwds in self.cases_missing_digestmod_in_constructor():
+ with self.subTest(args=args, kwds=kwds), catcher():
+ func(*args, **kwds)
+
+ def do_test_constructor_unknown_digestmod(self, catcher):
+ for func, args, kwds in self.cases_unknown_digestmod_in_constructor():
+ with self.subTest(args=args, kwds=kwds), catcher():
+ func(*args, **kwds)
+
+ def cases_missing_digestmod_in_constructor(self):
+ raise NotImplementedError
+
+ def make_missing_digestmod_cases(self, func, choices):
+ """Generate cases for missing digestmod tests."""
+ key, msg = b'unused key', b'unused msg'
+ cases = self._invalid_digestmod_cases(func, key, msg, choices)
+ return [(func, (key,), {}), (func, (key, msg), {})] + cases
+
+ def cases_unknown_digestmod_in_constructor(self):
+ raise NotImplementedError
+
+ def make_unknown_digestmod_cases(self, func, choices):
+ """Generate cases for unknown digestmod tests."""
+ key, msg = b'unused key', b'unused msg'
+ return self._invalid_digestmod_cases(func, key, msg, choices)
+
+ def _invalid_digestmod_cases(self, func, key, msg, choices):
+ cases = []
+ for digestmod in choices:
+ kwargs = {'digestmod': digestmod}
+ cases.append((func, (key,), kwargs))
+ cases.append((func, (key, msg), kwargs))
+ cases.append((func, (key,), kwargs | {'msg': msg}))
+ return cases
+
+
+class ConstructorTestCaseMixin(CreatorMixin, DigestMixin, CheckerMixin):
+ """HMAC constructor tests based on HMAC-SHA-2/256."""
+
+ key = b"key"
+ msg = b"hash this!"
+ res = "6c845b47f52b3b47f6590c502db7825aad757bf4fadc8fa972f7cd2e76a5bdeb"
+
+ def do_test_constructor(self, hmac_on_key_and_msg):
+ self.do_test_constructor_invalid_types(hmac_on_key_and_msg)
+ self.do_test_constructor_supported_types(hmac_on_key_and_msg)
+
+ def do_test_constructor_invalid_types(self, hmac_on_key_and_msg):
+ self.assertRaises(TypeError, hmac_on_key_and_msg, 1)
+ self.assertRaises(TypeError, hmac_on_key_and_msg, "key")
+
+ self.assertRaises(TypeError, hmac_on_key_and_msg, b"key", 1)
+ self.assertRaises(TypeError, hmac_on_key_and_msg, b"key", "msg")
+
+ def do_test_constructor_supported_types(self, hmac_on_key_and_msg):
+ for tp_key in [bytes, bytearray]:
+ for tp_msg in [bytes, bytearray, memoryview]:
+ with self.subTest(tp_key=tp_key, tp_msg=tp_msg):
+ h = hmac_on_key_and_msg(tp_key(self.key), tp_msg(self.msg))
+ self.assertEqual(h.name, "hmac-sha256")
+ self.assertEqual(h.hexdigest(), self.res)
+
+ @hashlib_helper.requires_hashdigest("sha256")
+ def test_constructor(self):
+ self.do_test_constructor(self.bind_hmac_new("sha256"))
+
+ @hashlib_helper.requires_hashdigest("sha256")
+ def test_digest(self):
+ digest = self.hmac_digest(self.key, self.msg, "sha256")
+ self.assertEqual(digest, binascii.unhexlify(self.res))
+
+
+class PyConstructorBaseMixin(PyModuleMixin,
+ DigestModTestCaseMixin,
+ ConstructorTestCaseMixin):
+
+ def cases_missing_digestmod_in_constructor(self):
+ func, choices = self.hmac_new, ['', None, False]
+ return self.make_missing_digestmod_cases(func, choices)
+
+ def cases_unknown_digestmod_in_constructor(self):
+ func, choices = self.hmac_new, ['unknown']
+ return self.make_unknown_digestmod_cases(func, choices)
+
+ @requires_builtin_sha2()
+ def test_constructor_with_module(self):
+ self.do_test_constructor(self.bind_hmac_new(sha2.sha256))
+
+ @requires_builtin_sha2()
+ def test_digest_with_module(self):
+ digest = self.hmac_digest(self.key, self.msg, sha2.sha256)
+ self.assertEqual(digest, binascii.unhexlify(self.res))
+
+
+class PyConstructorTestCase(ThroughObjectMixin, PyConstructorBaseMixin,
+ unittest.TestCase):
+ """Test the hmac.HMAC() pure Python constructor."""
+
+
+class PyModuleConstructorTestCase(ThroughModuleAPIMixin, PyConstructorBaseMixin,
+ unittest.TestCase):
+ """Test the hmac.new() and hmac.digest() functions."""
+
+ def test_hmac_digest_digestmod_parameter(self):
+ func = self.hmac_digest
+
+ def raiser():
+ raise RuntimeError("custom exception")
+
+ with self.assertRaisesRegex(RuntimeError, "custom exception"):
+ func(b'key', b'msg', raiser)
+
+ with self.assertRaisesRegex(ValueError, 'hash type'):
+ func(b'key', b'msg', 'unknown')
+
+ with self.assertRaisesRegex(AttributeError, 'new'):
+ func(b'key', b'msg', 1234)
+ with self.assertRaisesRegex(AttributeError, 'new'):
+ func(b'key', b'msg', None)
+
+
+class ExtensionConstructorTestCaseMixin(DigestModTestCaseMixin,
+ ConstructorTestCaseMixin):
+
+ # The underlying C class.
+ obj_type = None
+
+ # The exact exception class raised when a 'digestmod' parameter is invalid.
+ exc_type = None
- @unittest.skipUnless(C_HMAC is not None, 'need _hashlib')
def test_internal_types(self):
- # internal types like _hashlib.C_HMAC are not constructable
- check_disallow_instantiation(self, C_HMAC)
+ # internal C types are immutable and cannot be instantiated
+ check_disallow_instantiation(self, self.obj_type)
with self.assertRaisesRegex(TypeError, "immutable type"):
- C_HMAC.value = None
+ self.obj_type.value = None
- @unittest.skipUnless(sha256_module is not None, 'need _sha256')
- def test_with_sha256_module(self):
- h = hmac.HMAC(b"key", b"hash this!", digestmod=sha256_module.sha256)
- self.assertEqual(h.hexdigest(), self.expected)
- self.assertEqual(h.name, "hmac-sha256")
+ def assert_digestmod_error(self):
+ self.assertIsSubclass(self.exc_type, ValueError)
+ return self.assertRaises(self.exc_type)
- digest = hmac.digest(b"key", b"hash this!", sha256_module.sha256)
- self.assertEqual(digest, binascii.unhexlify(self.expected))
+ def test_constructor_missing_digestmod(self):
+ self.do_test_constructor_missing_digestmod(self.assert_digestmod_error)
+ def test_constructor_unknown_digestmod(self):
+ self.do_test_constructor_unknown_digestmod(self.assert_digestmod_error)
-class SanityTestCase(unittest.TestCase):
+ def cases_missing_digestmod_in_constructor(self):
+ func, choices = self.hmac_new, ['', None, False]
+ return self.make_missing_digestmod_cases(func, choices)
- @hashlib_helper.requires_hashdigest('sha256')
- def test_exercise_all_methods(self):
- # Exercising all methods once.
- # This must not raise any exceptions
- try:
- h = hmac.HMAC(b"my secret key", digestmod="sha256")
- h.update(b"compute the hash of this text!")
- h.digest()
- h.hexdigest()
- h.copy()
- except Exception:
- self.fail("Exception raised during normal usage of HMAC class.")
+ def cases_unknown_digestmod_in_constructor(self):
+ func, choices = self.hmac_new, ['unknown', 1234]
+ return self.make_unknown_digestmod_cases(func, choices)
-class UpdateTestCase(unittest.TestCase):
- @hashlib_helper.requires_hashdigest('sha256')
- def test_with_str_update(self):
- with self.assertRaises(TypeError):
- h = hmac.new(b"key", digestmod='sha256')
- h.update("invalid update")
+class OpenSSLConstructorTestCase(ThroughOpenSSLAPIMixin,
+ ExtensionConstructorTestCaseMixin,
+ unittest.TestCase):
+
+ @property
+ def obj_type(self):
+ return _hashlib.HMAC
+
+ @property
+ def exc_type(self):
+ return _hashlib.UnsupportedDigestmodError
+
+ def test_hmac_digest_digestmod_parameter(self):
+ # TODO(picnixz): remove default arguments in _hashlib.hmac_digest()
+ # since the return value is not a HMAC object but a bytes object.
+ for value in [object, 'unknown', 1234, None]:
+ with self.subTest(value=value), self.assert_digestmod_error():
+ self.hmac_digest(b'key', b'msg', value)
+
+
+class SanityTestCaseMixin(CreatorMixin):
+ """Sanity checks for HMAC objects and their object interface.
+
+ The tests here use a common digestname and do not check all supported
+ hash functions.
+ """
+
+ # The underlying HMAC class to test. May be in C or in Python.
+ hmac_class: type
+ # The underlying hash function name (should be accepted by the HMAC class).
+ digestname: str
+
+ def test_methods(self):
+ h = self.hmac_new(b"my secret key", digestmod=self.digestname)
+ self.assertIsInstance(h, self.hmac_class)
+ self.assertIsNone(h.update(b"compute the hash of this text!"))
+ self.assertIsInstance(h.digest(), bytes)
+ self.assertIsInstance(h.hexdigest(), str)
+ self.assertIsInstance(h.copy(), self.hmac_class)
+
+ def test_repr(self):
+ # HMAC object representation may differ across implementations
+ raise NotImplementedError
+
+(a)hashlib_helper.requires_hashdigest('sha256')
+class PySanityTestCase(ThroughObjectMixin, PyModuleMixin, SanityTestCaseMixin,
+ unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.hmac_class = cls.hmac.HMAC
+ cls.digestname = 'sha256'
+
+ def test_repr(self):
+ h = self.hmac_new(b"my secret key", digestmod=self.digestname)
+ self.assertStartsWith(repr(h), "<hmac.HMAC object at")
+
+
+(a)hashlib_helper.requires_hashdigest('sha256', openssl=True)
+class OpenSSLSanityTestCase(ThroughOpenSSLAPIMixin, SanityTestCaseMixin,
+ unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.hmac_class = _hashlib.HMAC
+ cls.digestname = 'sha256'
+
+ def test_repr(self):
+ h = self.hmac_new(b"my secret key", digestmod=self.digestname)
+ self.assertStartsWith(repr(h), f"<{self.digestname} HMAC object @")
+
+
+class UpdateTestCaseMixin:
+ """Tests for the update() method (streaming HMAC)."""
+
+ def HMAC(self, key, msg=None):
+ """Create a HMAC object."""
+ raise NotImplementedError
+
+ def test_update(self):
+ key, msg = random.randbytes(16), random.randbytes(16)
+ with self.subTest(key=key, msg=msg):
+ h1 = self.HMAC(key, msg)
+
+ h2 = self.HMAC(key)
+ h2.update(msg)
+
+ self.assertEqual(h1.digest(), h2.digest())
+ self.assertEqual(h1.hexdigest(), h2.hexdigest())
+
+ def test_update_exceptions(self):
+ h = self.HMAC(b"key")
+ for msg in ['invalid msg', 123, (), []]:
+ with self.subTest(msg=msg):
+ self.assertRaises(TypeError, h.update, msg)
+
+
+(a)hashlib_helper.requires_hashdigest('sha256')
+class PyUpdateTestCase(UpdateTestCaseMixin, unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.hmac = import_fresh_module('hmac', blocked=['_hashlib'])
+
+ def HMAC(self, key, msg=None):
+ return self.hmac.HMAC(key, msg, digestmod='sha256')
+
+
+(a)hashlib_helper.requires_hashlib()
+(a)hashlib_helper.requires_hashdigest('sha256', openssl=True)
+class OpenSSLUpdateTestCase(UpdateTestCaseMixin, unittest.TestCase):
+
+ def HMAC(self, key, msg=None):
+ return hmac.new(key, msg, digestmod='sha256')
+
+
+(a)hashlib_helper.requires_hashdigest('sha256')
class CopyTestCase(unittest.TestCase):
- @hashlib_helper.requires_hashdigest('sha256')
def test_attributes_old(self):
# Testing if attributes are of same type.
h1 = hmac.HMAC.__new__(hmac.HMAC)
h1._init_old(b"key", b"msg", digestmod="sha256")
h2 = h1.copy()
- self.assertEqual(type(h1._inner), type(h2._inner),
- "Types of inner don't match.")
- self.assertEqual(type(h1._outer), type(h2._outer),
- "Types of outer don't match.")
+ self.assertEqual(type(h1._inner), type(h2._inner))
+ self.assertEqual(type(h1._outer), type(h2._outer))
- @hashlib_helper.requires_hashdigest('sha256')
def test_realcopy_old(self):
# Testing if the copy method created a real copy.
h1 = hmac.HMAC.__new__(hmac.HMAC)
h1._init_old(b"key", b"msg", digestmod="sha256")
+ self.assertIsNone(h1._hmac)
+
h2 = h1.copy()
+ self.assertIsNone(h2._hmac)
# Using id() in case somebody has overridden __eq__/__ne__.
- self.assertTrue(id(h1) != id(h2), "No real copy of the HMAC instance.")
- self.assertTrue(id(h1._inner) != id(h2._inner),
- "No real copy of the attribute 'inner'.")
- self.assertTrue(id(h1._outer) != id(h2._outer),
- "No real copy of the attribute 'outer'.")
- self.assertIs(h1._hmac, None)
-
- @unittest.skipIf(_hashopenssl is None, "test requires _hashopenssl")
- @hashlib_helper.requires_hashdigest('sha256')
+ self.assertNotEqual(id(h1), id(h2))
+ self.assertNotEqual(id(h1._inner), id(h2._inner))
+ self.assertNotEqual(id(h1._outer), id(h2._outer))
+
+ @hashlib_helper.requires_hashlib()
def test_realcopy_hmac(self):
h1 = hmac.HMAC.__new__(hmac.HMAC)
h1._init_hmac(b"key", b"msg", digestmod="sha256")
h2 = h1.copy()
- self.assertTrue(id(h1._hmac) != id(h2._hmac))
+ self.assertNotEqual(id(h1._hmac), id(h2._hmac))
- @hashlib_helper.requires_hashdigest('sha256')
def test_equality(self):
# Testing if the copy has the same digests.
h1 = hmac.HMAC(b"key", digestmod="sha256")
h1.update(b"some random text")
h2 = h1.copy()
- self.assertEqual(h1.digest(), h2.digest(),
- "Digest of copy doesn't match original digest.")
- self.assertEqual(h1.hexdigest(), h2.hexdigest(),
- "Hexdigest of copy doesn't match original hexdigest.")
+ self.assertEqual(h1.digest(), h2.digest())
+ self.assertEqual(h1.hexdigest(), h2.hexdigest())
- @hashlib_helper.requires_hashdigest('sha256')
def test_equality_new(self):
# Testing if the copy has the same digests with hmac.new().
h1 = hmac.new(b"key", digestmod="sha256")
h1.update(b"some random text")
h2 = h1.copy()
- self.assertTrue(
- id(h1) != id(h2), "No real copy of the HMAC instance."
- )
- self.assertEqual(h1.digest(), h2.digest(),
- "Digest of copy doesn't match original digest.")
- self.assertEqual(h1.hexdigest(), h2.hexdigest(),
- "Hexdigest of copy doesn't match original hexdigest.")
-
-
-class CompareDigestTestCase(unittest.TestCase):
-
- def test_hmac_compare_digest(self):
- self._test_compare_digest(hmac.compare_digest)
- if openssl_compare_digest is not None:
- self.assertIs(hmac.compare_digest, openssl_compare_digest)
- else:
- self.assertIs(hmac.compare_digest, operator_compare_digest)
-
- def test_operator_compare_digest(self):
- self._test_compare_digest(operator_compare_digest)
-
- @unittest.skipIf(openssl_compare_digest is None, "test requires _hashlib")
- def test_openssl_compare_digest(self):
- self._test_compare_digest(openssl_compare_digest)
-
- def _test_compare_digest(self, compare_digest):
- # Testing input type exception handling
- a, b = 100, 200
- self.assertRaises(TypeError, compare_digest, a, b)
- a, b = 100, b"foobar"
- self.assertRaises(TypeError, compare_digest, a, b)
- a, b = b"foobar", 200
- self.assertRaises(TypeError, compare_digest, a, b)
- a, b = "foobar", b"foobar"
- self.assertRaises(TypeError, compare_digest, a, b)
- a, b = b"foobar", "foobar"
- self.assertRaises(TypeError, compare_digest, a, b)
-
+ self.assertNotEqual(id(h1), id(h2))
+ self.assertEqual(h1.digest(), h2.digest())
+ self.assertEqual(h1.hexdigest(), h2.hexdigest())
+
+
+class CompareDigestMixin:
+
+ @staticmethod
+ def compare_digest(a, b):
+ """Implementation of 'a == b' to test."""
+ raise NotImplementedError
+
+ def assert_digest_equal(self, a, b):
+ with self.subTest(a=a, b=b):
+ self.assertTrue(self.compare_digest(a, b))
+ with self.subTest(a=b, b=a):
+ self.assertTrue(self.compare_digest(b, a))
+
+ def assert_digest_not_equal(self, a, b):
+ with self.subTest(a=a, b=b):
+ self.assertFalse(self.compare_digest(a, b))
+ with self.subTest(a=b, b=a):
+ self.assertFalse(self.compare_digest(b, a))
+
+ def test_exceptions(self):
+ for a, b in [
+ # Testing input type exception handling
+ (100, 200), (100, b"foobar"), ("foobar", b"foobar"),
+ # non-ASCII strings
+ ("fooä", "fooä")
+ ]:
+ self.assertRaises(TypeError, self.compare_digest, a, b)
+ self.assertRaises(TypeError, self.compare_digest, b, a)
+
+ def test_bytes(self):
# Testing bytes of different lengths
a, b = b"foobar", b"foo"
- self.assertFalse(compare_digest(a, b))
+ self.assert_digest_not_equal(a, b)
a, b = b"\xde\xad\xbe\xef", b"\xde\xad"
- self.assertFalse(compare_digest(a, b))
+ self.assert_digest_not_equal(a, b)
# Testing bytes of same lengths, different values
a, b = b"foobar", b"foobaz"
- self.assertFalse(compare_digest(a, b))
+ self.assert_digest_not_equal(a, b)
a, b = b"\xde\xad\xbe\xef", b"\xab\xad\x1d\xea"
- self.assertFalse(compare_digest(a, b))
+ self.assert_digest_not_equal(a, b)
# Testing bytes of same lengths, same values
a, b = b"foobar", b"foobar"
- self.assertTrue(compare_digest(a, b))
+ self.assert_digest_equal(a, b)
a, b = b"\xde\xad\xbe\xef", b"\xde\xad\xbe\xef"
- self.assertTrue(compare_digest(a, b))
+ self.assert_digest_equal(a, b)
+ def test_bytearray(self):
# Testing bytearrays of same lengths, same values
a, b = bytearray(b"foobar"), bytearray(b"foobar")
- self.assertTrue(compare_digest(a, b))
+ self.assert_digest_equal(a, b)
# Testing bytearrays of different lengths
a, b = bytearray(b"foobar"), bytearray(b"foo")
- self.assertFalse(compare_digest(a, b))
+ self.assert_digest_not_equal(a, b)
# Testing bytearrays of same lengths, different values
a, b = bytearray(b"foobar"), bytearray(b"foobaz")
- self.assertFalse(compare_digest(a, b))
+ self.assert_digest_not_equal(a, b)
+ def test_mixed_types(self):
# Testing byte and bytearray of same lengths, same values
a, b = bytearray(b"foobar"), b"foobar"
- self.assertTrue(compare_digest(a, b))
- self.assertTrue(compare_digest(b, a))
+ self.assert_digest_equal(a, b)
# Testing byte bytearray of different lengths
a, b = bytearray(b"foobar"), b"foo"
- self.assertFalse(compare_digest(a, b))
- self.assertFalse(compare_digest(b, a))
+ self.assert_digest_not_equal(a, b)
# Testing byte and bytearray of same lengths, different values
a, b = bytearray(b"foobar"), b"foobaz"
- self.assertFalse(compare_digest(a, b))
- self.assertFalse(compare_digest(b, a))
+ self.assert_digest_not_equal(a, b)
+ def test_string(self):
# Testing str of same lengths
a, b = "foobar", "foobar"
- self.assertTrue(compare_digest(a, b))
+ self.assert_digest_equal(a, b)
# Testing str of different lengths
a, b = "foo", "foobar"
- self.assertFalse(compare_digest(a, b))
+ self.assert_digest_not_equal(a, b)
- # Testing bytes of same lengths, different values
+ # Testing str of same lengths, different values
a, b = "foobar", "foobaz"
- self.assertFalse(compare_digest(a, b))
-
- # Testing error cases
- a, b = "foobar", b"foobar"
- self.assertRaises(TypeError, compare_digest, a, b)
- a, b = b"foobar", "foobar"
- self.assertRaises(TypeError, compare_digest, a, b)
- a, b = b"foobar", 1
- self.assertRaises(TypeError, compare_digest, a, b)
- a, b = 100, 200
- self.assertRaises(TypeError, compare_digest, a, b)
- a, b = "fooä", "fooä"
- self.assertRaises(TypeError, compare_digest, a, b)
-
- # subclasses are supported by ignore __eq__
- class mystr(str):
+ self.assert_digest_not_equal(a, b)
+
+ def test_string_subclass(self):
+ class S(str):
def __eq__(self, other):
- return False
+ raise ValueError("should not be called")
- a, b = mystr("foobar"), mystr("foobar")
- self.assertTrue(compare_digest(a, b))
- a, b = mystr("foobar"), "foobar"
- self.assertTrue(compare_digest(a, b))
- a, b = mystr("foobar"), mystr("foobaz")
- self.assertFalse(compare_digest(a, b))
+ a, b = S("foobar"), S("foobar")
+ self.assert_digest_equal(a, b)
+ a, b = S("foobar"), "foobar"
+ self.assert_digest_equal(a, b)
+ a, b = S("foobar"), S("foobaz")
+ self.assert_digest_not_equal(a, b)
- class mybytes(bytes):
+ def test_bytes_subclass(self):
+ class B(bytes):
def __eq__(self, other):
- return False
-
- a, b = mybytes(b"foobar"), mybytes(b"foobar")
- self.assertTrue(compare_digest(a, b))
- a, b = mybytes(b"foobar"), b"foobar"
- self.assertTrue(compare_digest(a, b))
- a, b = mybytes(b"foobar"), mybytes(b"foobaz")
- self.assertFalse(compare_digest(a, b))
+ raise ValueError("should not be called")
+
+ a, b = B(b"foobar"), B(b"foobar")
+ self.assert_digest_equal(a, b)
+ a, b = B(b"foobar"), b"foobar"
+ self.assert_digest_equal(a, b)
+ a, b = B(b"foobar"), B(b"foobaz")
+ self.assert_digest_not_equal(a, b)
+
+
+class HMACCompareDigestTestCase(CompareDigestMixin, unittest.TestCase):
+ compare_digest = hmac.compare_digest
+
+ def test_compare_digest_func(self):
+ if openssl_compare_digest is not None:
+ self.assertIs(self.compare_digest, openssl_compare_digest)
+ else:
+ self.assertIs(self.compare_digest, operator_compare_digest)
+
+
+(a)hashlib_helper.requires_hashlib()
+class OpenSSLCompareDigestTestCase(CompareDigestMixin, unittest.TestCase):
+ compare_digest = openssl_compare_digest
+
+
+class OperatorCompareDigestTestCase(CompareDigestMixin, unittest.TestCase):
+ compare_digest = operator_compare_digest
if __name__ == "__main__":
1
0