Skip to content

Commit d658b90

Browse files
authored
gh-136912: fix handling of OverflowError in hmac.digest (#136917)
The OpenSSL and HACL* implementations of HMAC single-shot digest computation reject keys whose length exceeds `INT_MAX` and `UINT32_MAX` respectively. The OpenSSL implementation also rejects messages whose length exceed `INT_MAX`. Using such keys in `hmac.digest` previously raised an `OverflowError` which was propagated to the caller. This commit mitigates this case by making `hmac.digest` fall back to HMAC's pure Python implementation which accepts arbitrary large keys or messages. This change only affects the top-level entrypoint `hmac.digest`, leaving `_hashopenssl.hmac_digest` and `_hmac.compute_digest` untouched.
1 parent f7c380e commit d658b90

File tree

3 files changed

+70
-9
lines changed

3 files changed

+70
-9
lines changed

Lib/hmac.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,13 +241,25 @@ 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:
245+
# OpenSSL's HMAC limits the size of the key to INT_MAX.
246+
# Instead of falling back to HACL* implementation which
247+
# may still not be supported due to a too large key, we
248+
# directly switch to the pure Python fallback instead
249+
# even if we could have used streaming HMAC for small keys
250+
# but large messages.
251+
return _compute_digest_fallback(key, msg, digest)
244252
except _hashopenssl.UnsupportedDigestmodError:
245253
pass
246254

247255
if _hmac and isinstance(digest, str):
248256
try:
249257
return _hmac.compute_digest(key, msg, digest)
250258
except (OverflowError, _hmac.UnknownHashError):
259+
# HACL* HMAC limits the size of the key to UINT32_MAX
260+
# so we fallback to the pure Python implementation even
261+
# if streaming HMAC may have been used for small keys
262+
# and large messages.
251263
pass
252264

253265
return _compute_digest_fallback(key, msg, digest)

Lib/test/test_hmac.py

Lines changed: 55 additions & 9 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
27-
import unittest.mock as mock
2826
import warnings
2927
from _operator import _compare_digest as operator_compare_digest
28+
from test.support import _4G, bigmemtest
3029
from test.support import check_disallow_instantiation
30+
from test.support import hashlib_helper, import_helper
3131
from test.support.hashlib_helper import (
3232
BuiltinHashFunctionsTrait,
3333
HashFunctionsTrait,
3434
NamedHashFunctionsTrait,
3535
OpenSSLHashFunctionsTrait,
3636
)
37-
from test.support.import_helper import import_fresh_module, import_module
37+
from test.support.import_helper import import_fresh_module
38+
from unittest.mock import patch
3839

3940
try:
4041
import _hashlib
@@ -727,7 +728,7 @@ def setUpClass(cls):
727728
super().setUpClass()
728729
for meth in ['_init_openssl_hmac', '_init_builtin_hmac']:
729730
fn = getattr(cls.hmac.HMAC, meth)
730-
cm = mock.patch.object(cls.hmac.HMAC, meth, autospec=True, wraps=fn)
731+
cm = patch.object(cls.hmac.HMAC, meth, autospec=True, wraps=fn)
731732
cls.enterClassContext(cm)
732733

733734
@classmethod
@@ -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
@@ -1445,9 +1450,8 @@ def test_hmac_constructor_uses_builtin(self):
14451450
hmac = import_fresh_module("hmac", blocked=["_hashlib"])
14461451

14471452
def watch_method(cls, name):
1448-
return mock.patch.object(
1449-
cls, name, autospec=True, wraps=getattr(cls, name)
1450-
)
1453+
wraps = getattr(cls, name)
1454+
return patch.object(cls, name, autospec=True, wraps=wraps)
14511455

14521456
with (
14531457
watch_method(hmac.HMAC, '_init_openssl_hmac') as f,
@@ -1499,6 +1503,48 @@ def test_with_fallback(self):
14991503
finally:
15001504
cache.pop('foo')
15011505

1506+
@hashlib_helper.requires_openssl_hashdigest("md5")
1507+
@bigmemtest(size=_4G + 5, memuse=2, dry_run=False)
1508+
def test_hmac_digest_overflow_error_openssl_only(self, size):
1509+
hmac = import_fresh_module("hmac", blocked=["_hmac"])
1510+
self.do_test_hmac_digest_overflow_error_switch_to_slow(hmac, size)
1511+
1512+
@hashlib_helper.requires_builtin_hashdigest("_md5", "md5")
1513+
@bigmemtest(size=_4G + 5, memuse=2, dry_run=False)
1514+
def test_hmac_digest_overflow_error_builtin_only(self, size):
1515+
hmac = import_fresh_module("hmac", blocked=["_hashlib"])
1516+
self.do_test_hmac_digest_overflow_error_switch_to_slow(hmac, size)
1517+
1518+
def do_test_hmac_digest_overflow_error_switch_to_slow(self, hmac, size):
1519+
"""Check that hmac.digest() falls back to pure Python.
1520+
1521+
The *hmac* argument implements the HMAC module interface.
1522+
The *size* argument is a large key size or message size that would
1523+
trigger an OverflowError in the C implementation(s) of hmac.digest().
1524+
"""
1525+
1526+
bigkey = b'K' * size
1527+
bigmsg = b'M' * size
1528+
1529+
with patch.object(hmac, "_compute_digest_fallback") as slow:
1530+
hmac.digest(bigkey, b'm', "md5")
1531+
slow.assert_called_once()
1532+
1533+
with patch.object(hmac, "_compute_digest_fallback") as slow:
1534+
hmac.digest(b'k', bigmsg, "md5")
1535+
slow.assert_called_once()
1536+
1537+
@hashlib_helper.requires_hashdigest("md5", openssl=True)
1538+
@bigmemtest(size=_4G + 5, memuse=2, dry_run=False)
1539+
def test_hmac_digest_no_overflow_error_in_fallback(self, size):
1540+
hmac = import_fresh_module("hmac", blocked=["_hashlib", "_hmac"])
1541+
1542+
for key, msg in [(b'K' * size, b'm'), (b'k', b'M' * size)]:
1543+
with self.subTest(keysize=len(key), msgsize=len(msg)):
1544+
with patch.object(hmac, "_compute_digest_fallback") as slow:
1545+
hmac.digest(key, msg, "md5")
1546+
slow.assert_called_once()
1547+
15021548

15031549
class BuiltinMiscellaneousTests(BuiltinModuleMixin, unittest.TestCase):
15041550
"""HMAC-BLAKE2 is not standardized as BLAKE2 is a keyed hash function.
@@ -1511,7 +1557,7 @@ class BuiltinMiscellaneousTests(BuiltinModuleMixin, unittest.TestCase):
15111557
@classmethod
15121558
def setUpClass(cls):
15131559
super().setUpClass()
1514-
cls.blake2 = import_module("_blake2")
1560+
cls.blake2 = import_helper.import_module("_blake2")
15151561
cls.blake2b = cls.blake2.blake2b
15161562
cls.blake2s = cls.blake2.blake2s
15171563

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:func:`hmac.digest` now properly handles large keys and messages
2+
by falling back to the pure Python implementation when necessary.
3+
Patch by Bénédikt Tran.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

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

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


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy