Skip to content

Commit d0079c6

Browse files
committed
fix how large messages and keys are rejected by HMAC
1 parent 1e67293 commit d0079c6

File tree

3 files changed

+65
-5
lines changed

3 files changed

+65
-5
lines changed

Lib/hmac.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,13 +241,20 @@ def digest(key, msg, digest):
241241
if _hashopenssl and isinstance(digest, (str, _functype)):
242242
try:
243243
return _hashopenssl.hmac_digest(key, msg, digest)
244+
except OverflowError as exc:
245+
if not isinstance(digest, str):
246+
# Without re-raising now, slow HMAC will be used as
247+
# HACL* HMAC only identifies digests by their name.
248+
raise exc
244249
except _hashopenssl.UnsupportedDigestmodError:
245250
pass
246251

247252
if _hmac and isinstance(digest, str):
248253
try:
254+
# The following call may raise an OverflowError
255+
# but we do not want to fallback to slow HMAC.
249256
return _hmac.compute_digest(key, msg, digest)
250-
except (OverflowError, _hmac.UnknownHashError):
257+
except _hmac.UnknownHashError:
251258
pass
252259

253260
return _compute_digest_fallback(key, msg, digest)

Lib/test/test_hmac.py

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,21 @@
2121
import hmac
2222
import hashlib
2323
import random
24-
import test.support.hashlib_helper as hashlib_helper
2524
import types
2625
import unittest
2726
import unittest.mock as mock
2827
import warnings
2928
from _operator import _compare_digest as operator_compare_digest
29+
from test.support import _4G, bigmemtest
3030
from test.support import check_disallow_instantiation
31+
from test.support import hashlib_helper, import_helper
3132
from test.support.hashlib_helper import (
3233
BuiltinHashFunctionsTrait,
3334
HashFunctionsTrait,
3435
NamedHashFunctionsTrait,
3536
OpenSSLHashFunctionsTrait,
3637
)
37-
from test.support.import_helper import import_fresh_module, import_module
38+
from test.support.import_helper import import_fresh_module
3839

3940
try:
4041
import _hashlib
@@ -949,7 +950,11 @@ class PyConstructorTestCase(ThroughObjectMixin, PyConstructorBaseMixin,
949950

950951
class PyModuleConstructorTestCase(ThroughModuleAPIMixin, PyConstructorBaseMixin,
951952
unittest.TestCase):
952-
"""Test the hmac.new() and hmac.digest() functions."""
953+
"""Test the hmac.new() and hmac.digest() functions.
954+
955+
Note that "self.hmac" is imported by blocking "_hashlib" and "_hmac".
956+
For testing functions in "hmac", extend PyMiscellaneousTests instead.
957+
"""
953958

954959
def test_hmac_digest_digestmod_parameter(self):
955960
func = self.hmac_digest
@@ -1499,6 +1504,51 @@ def test_with_fallback(self):
14991504
finally:
15001505
cache.pop('foo')
15011506

1507+
@hashlib_helper.requires_builtin_hashdigest("_md5", "md5")
1508+
@bigmemtest(size=_4G, memuse=2, dry_run=False)
1509+
def test_hmac_digest_overflow_error_no_openssl(self, size):
1510+
hmac = import_fresh_module("hmac", blocked=["_hashlib"])
1511+
1512+
UINT32_MAX = (1 << 32) - 1
1513+
self.assertEqual(UINT32_MAX, size - 1)
1514+
bigkey = b'K' * UINT32_MAX
1515+
bigmsg = b'M' * UINT32_MAX
1516+
1517+
with unittest.mock.patch.object(hmac, "_compute_digest_fallback") as f:
1518+
self.assertRaises(OverflowError, hmac.digest, bigkey, b'm', "md5")
1519+
self.assertRaises(OverflowError, hmac.digest, b'k', bigmsg, "md5")
1520+
f.assert_not_called()
1521+
1522+
@hashlib_helper.requires_openssl_hashdigest("md5")
1523+
@bigmemtest(size=_4G, memuse=2, dry_run=False)
1524+
def test_hmac_digest_overflow_error_no_builtin(self, size):
1525+
capi = import_helper.import_module("_testcapi")
1526+
hmac = import_fresh_module("hmac", blocked=["_hmac"])
1527+
1528+
INT_MAX = capi.INT_MAX
1529+
self.assertLessEqual(INT_MAX, size)
1530+
bigkey = b'K' * INT_MAX
1531+
bigmsg = b'M' * INT_MAX
1532+
1533+
with unittest.mock.patch.object(hmac, "_compute_digest_fallback") as f:
1534+
self.assertRaises(OverflowError, hmac.digest, bigkey, b'm', "md5")
1535+
self.assertRaises(OverflowError, hmac.digest, b'k', bigmsg, "md5")
1536+
f.assert_not_called()
1537+
1538+
@hashlib_helper.requires_hashdigest("md5", openssl=True)
1539+
@bigmemtest(size=_4G + 1, memuse=2, dry_run=False)
1540+
def test_hmac_digest_no_overflow_error_in_fallback(self, size):
1541+
hmac = import_fresh_module("hmac", blocked=["_hashlib", "_hmac"])
1542+
1543+
for key, msg in [(b'K' * size, b'm'), (b'k', b'M' * size)]:
1544+
with self.subTest(keysize=len(key), msgsize=len(msg)):
1545+
with unittest.mock.patch.object(
1546+
hmac, "_compute_digest_fallback",
1547+
wraps=hmac._compute_digest_fallback,
1548+
) as f:
1549+
self.assertIsInstance(hmac.digest(key, msg, "md5"), bytes)
1550+
f.assert_called_once()
1551+
15021552

15031553
class BuiltinMiscellaneousTests(BuiltinModuleMixin, unittest.TestCase):
15041554
"""HMAC-BLAKE2 is not standardized as BLAKE2 is a keyed hash function.
@@ -1511,7 +1561,7 @@ class BuiltinMiscellaneousTests(BuiltinModuleMixin, unittest.TestCase):
15111561
@classmethod
15121562
def setUpClass(cls):
15131563
super().setUpClass()
1514-
cls.blake2 = import_module("_blake2")
1564+
cls.blake2 = import_helper.import_module("_blake2")
15151565
cls.blake2b = cls.blake2.blake2b
15161566
cls.blake2s = cls.blake2.blake2s
15171567

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:func:`hmac.digest` now properly rejects keys and messages whose length
2+
exceeds its capabililties. The exact limit is implementation-defined. Patch
3+
by Bénédikt Tran.

0 commit comments

Comments
 (0)