8000 gh-132097: allow AC to disable fastcall convention to avoid UBSan failures by picnixz · Pull Request #131605 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-132097: allow AC to disable fastcall convention to avoid UBSan failures #131605

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Apr 18, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
gh-99108: Implement HACL* HMAC (#130157)
A new extension module, `_hmac`, now exposes the HACL* HMAC (formally verified) implementation.

The HACL* implementation is used as a fallback implementation when the OpenSSL implementation of HMAC
is not available or disabled. For now, only named hash algorithms are recognized and SIMD support provided
by HACL* for the BLAKE2 hash functions is not yet used.
  • Loading branch information
picnixz committed Apr 5, 2025
commit e115ce3be9d28c0d7fb3ac0627439b33d809a121
16 changes: 16 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,13 @@ Other language changes
The testbed can also be used to run the test suite of projects other than
CPython itself. (Contributed by Russell Keith-Magee in :gh:`127592`.)

* Add a built-in implementation for HMAC (:rfc:`2104`) using formally verified
code from the `HACL* <https://github.com/hacl-star/hacl-star/>`__ project.
This implementation is used as a fallback when the OpenSSL implementation
of HMAC is not available.
(Contributed by Bénédikt Tran in :gh:`99108`.)


.. _whatsnew314-pep765:

PEP 765: Disallow return/break/continue that exit a finally block
Expand All @@ -464,6 +471,7 @@ The compiler emits a :exc:`SyntaxWarning` when a :keyword:`return`, :keyword:`br
:keyword:`continue` statements appears where it exits a :keyword:`finally` block.
This change is specified in :pep:`765`.


New modules
===========

Expand Down Expand Up @@ -705,6 +713,14 @@ graphlib
(Contributed by Daniel Pope in :gh:`130914`)


hmac
----

* Add a built-in implementation for HMAC (:rfc:`2104`) using formally verified
code from the `HACL* <https://github.com/hacl-star/hacl-star/>`__ project.
(Contributed by Bénédikt Tran in :gh:`99108`.)


http
----

Expand Down
96 changes: 66 additions & 30 deletions Lib/hmac.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
Implements the HMAC algorithm as described by RFC 2104.
"""

import warnings as _warnings
try:
import _hashlib as _hashopenssl
except ImportError:
Expand All @@ -14,7 +13,10 @@
compare_digest = _hashopenssl.compare_digest
_functype = type(_hashopenssl.openssl_sha256) # builtin type

import hashlib as _hashlib
try:
import _hmac
except ImportError:
_hmac = None

trans 6D40 _5C = bytes((x ^ 0x5C) for x in range(256))
trans_36 = bytes((x ^ 0x36) for x in range(256))
Expand All @@ -24,11 +26,27 @@
digest_size = None


def _get_digest_constructor(digest_like):
if callable(digest_like):
return digest_like
if isinstance(digest_like, str):
def digest_wrapper(d=b''):
import hashlib
return hashlib.new(digest_like, d)
else:
def digest_wrapper(d=b''):
return digest_like.new(d)
return digest_wrapper


class HMAC:
"""RFC 2104 HMAC class. Also complies with RFC 4231.

This supports the API for Cryptographic Hash Functions (PEP 247).
"""

# Note: self.blocksize is the default blocksize; self.block_size
# is effective block size as well as the public API attribute.
blocksize = 64 # 512-bit HMAC; can be changed in subclasses.

__slots__ = (
Expand All @@ -50,32 +68,47 @@ def __init__(self, key, msg=None, digestmod=''):
"""

if not isinstance(key, (bytes, bytearray)):
raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__)
raise TypeError(f"key: expected bytes or bytearray, "
f"but got {type(key).__name__!r}")

if not digestmod:
raise TypeError("Missing required argument 'digestmod'.")

self.__init(key, msg, digestmod)

def __init(self, key, msg, digestmod):
if _hashopenssl and isinstance(digestmod, (str, _functype)):
try:
self._init_hmac(key, msg, digestmod)
self._init_openssl_hmac(key, msg, digestmod)
return
except _hashopenssl.UnsupportedDigestmodError:
self._init_old(key, msg, digestmod)
else:
self._init_old(key, msg, digestmod)
pass
if _hmac and isinstance(digestmod, str):
try:
self._init_builtin_hmac(key, msg, digestmod)
return
except _hmac.UnknownHashError:
pass
self._init_old(key, msg, digestmod)

def _init_hmac(self, key, msg, digestmod):
def _init_openssl_hmac(self, key, msg, digestmod):
self._hmac = _hashopenssl.hmac_new(key, msg, digestmod=digestmod)
self._inner = self._outer = None # because the slots are defined
self.digest_size = self._hmac.digest_size
self.block_size = self._hmac.block_size

_init_hmac = _init_openssl_hmac # for backward compatibility (if any)

def _init_builtin_hmac(self, key, msg, digestmod):
self._hmac = _hmac.new(key, msg, digestmod=digestmod)
self._inner = self._outer = None # because the slots are defined
self.digest_size = self._hmac.digest_size
self.block_size = self._hmac.block_size

def _init_old(self, key, msg, digestmod):
if callable(digestmod):
digest_cons = digestmod
elif isinstance(digestmod, str):
digest_cons = lambda d=b'': _hashlib.new(digestmod, d)
else:
digest_cons = lambda d=b'': digestmod.new(d)
import warnings

digest_cons = _get_digest_constructor(digestmod)

self._hmac = None
self._outer = digest_cons()
Expand All @@ -85,21 +118,19 @@ def _init_old(self, key, msg, digestmod):
if hasattr(self._inner, 'block_size'):
blocksize = self._inner.block_size
if blocksize < 16:
_warnings.warn('block_size of %d seems too small; using our '
'default of %d.' % (blocksize, self.blocksize),
RuntimeWarning, 2)
warnings.warn(f"block_size of {blocksize} seems too small; "
f"using our default of {self.blocksize}.",
RuntimeWarning, 2)
blocksize = self.blocksize
else:
_warnings.warn('No block_size attribute on given digest object; '
'Assuming %d.' % (self.blocksize),
RuntimeWarning, 2)
F438 warnings.warn("No block_size attribute on given digest object; "
f"Assuming {self.blocksize}.",
RuntimeWarning, 2)
blocksize = self.blocksize

if len(key) > blocksize:
key = digest_cons(key).digest()

# self.blocksize is the default blocksize. self.block_size is
# effective block size as well as the public API attribute.
self.block_size = blocksize

key = key.ljust(blocksize, b'\0')
Expand Down Expand Up @@ -165,6 +196,7 @@ def hexdigest(self):
h = self._current()
return h.hexdigest()


def new(key, msg=None, digestmod=''):
"""Create a new hashing object and return it.

Expand Down Expand Up @@ -194,25 +226,29 @@ def digest(key, msg, digest):
A hashlib constructor returning a new hash object. *OR*
A module supporting PEP 247.
"""
if _hashopenssl is not None and isinstance(digest, (str, _functype)):
if _hashopenssl and isinstance(digest, (str, _functype)):
try:
return _hashopenssl.hmac_digest(key, msg, digest)
except _hashopenssl.UnsupportedDigestmodError:
pass

if callable(digest):
digest_cons = digest
elif isinstance(digest, str):
digest_cons = lambda d=b'': _hashlib.new(digest, d)
else:
digest_cons = lambda d=b'': digest.new(d)
if _hmac and isinstance(digest, str):
try:
return _hmac.compute_digest(key, msg, digest)
except (OverflowError, _hmac.UnknownHashError):
pass

return _compute_digest_fallback(key, msg, digest)


def _compute_digest_fallback(key, msg, digest):
digest_cons = _get_digest_constructor(digest)
inner = digest_cons()
outer = digest_cons()
blocksize = getattr(inner, 'block_size', 64)
if len(key) > blocksize:
key = digest_cons(key).digest()
key = key + b'\x00' * (blocksize - len(key))
key = key.ljust(blocksize, b'\0')
inner.update(key.translate(trans_36))
outer.update(key.translate(trans_5C))
inner.update(msg)
Expand Down
9 changes: 9 additions & 0 deletions Lib/test/support/hashlib_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,20 @@
except ImportError:
_hashlib = None

try:
import _hmac
except ImportError:
_hmac = None


def requires_hashlib():
return unittest.skipIf(_hashlib is None, "requires _hashlib")


def requires_builtin_hmac():
return unittest.skipIf(_hmac is None, "requires _hmac")


def _decorate_func_or_class(func_or_class, decorator_func):
if not isinstance(func_or_class, type):
return decorator_func(func_or_class)
Expand Down
Loading
0