# Copyright (c) 2016-2021 by Ron Frederick and others. # # This program and the accompanying materials are made available under # the terms of the Eclipse Public License v2.0 which accompanies this # distribution and is available at: # # http://www.eclipse.org/legal/epl-2.0/ # # This program may also be made available under the following secondary # licenses when the conditions for such availability set forth in the # Eclipse Public License v2.0 are satisfied: # # GNU General Public License, Version 2.0, or any later versions of # that license # # SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later # # Contributors: # Ron Frederick - initial implementation, API, and documentation """UMAC cryptographic hash (RFC 4418) wrapper for Nettle library""" import binascii import ctypes import ctypes.util from typing import TYPE_CHECKING, Callable, Optional if TYPE_CHECKING: _ByteArray = ctypes.Array[ctypes.c_char] _SetKey = Callable[[_ByteArray, bytes], None] _SetNonce = Callable[[_ByteArray, ctypes.c_size_t, bytes], None] _Update = Callable[[_ByteArray, ctypes.c_size_t, bytes], None] _Digest = Callable[[_ByteArray, ctypes.c_size_t, _ByteArray], None] _New = Callable[[bytes, Optional[bytes], Optional[bytes]], object] _UMAC_BLOCK_SIZE = 1024 _UMAC_DEFAULT_CTX_SIZE = 4096 def _build_umac(size: int) -> '_New': """Function to build UMAC wrapper for a specific digest size""" _name = 'umac%d' % size _prefix = 'nettle_%s_' % _name try: _context_size: int = getattr(_nettle, _prefix + '_ctx_size')() except AttributeError: _context_size = _UMAC_DEFAULT_CTX_SIZE _set_key: _SetKey = getattr(_nettle, _prefix + 'set_key') _set_nonce: _SetNonce = getattr(_nettle, _prefix + 'set_nonce') _update: _Update = getattr(_nettle, _prefix + 'update') _digest: _Digest = getattr(_nettle, _prefix + 'digest') class _UMAC: """Wrapper for UMAC cryptographic hash This class supports the cryptographic hash API defined in PEP 452. """ name = _name block_size = _UMAC_BLOCK_SIZE digest_size = size // 8 def __init__(self, ctx: '_ByteArray', nonce: Optional[bytes] = None, msg: Optional[bytes] = None): self._ctx = ctx if nonce: self.set_nonce(nonce) if msg: self.update(msg) @classmethod def new(cls, key: bytes, msg: Optional[bytes] = None, nonce: Optional[bytes] = None) -> '_UMAC': """Construct a new UMAC hash object""" ctx = ctypes.create_string_buffer(_context_size) _set_key(ctx, key) return cls(ctx, nonce, msg) def copy(self) -> '_UMAC': """Return a new hash object with this object's state""" ctx = ctypes.create_string_buffer(self._ctx.raw) return self.__class__(ctx) def set_nonce(self, nonce: bytes) -> None: """Reset the nonce associated with this object""" _set_nonce(self._ctx, ctypes.c_size_t(len(nonce)), nonce) def update(self, msg: bytes) -> None: """Add the data in msg to the hash""" _update(self._ctx, ctypes.c_size_t(len(msg)), msg) def digest(self) -> bytes: """Return the hash and increment nonce to begin a new message .. note:: The hash is reset and the nonce is incremented when this function is called. This doesn't match the behavior defined in PEP 452. """ result = ctypes.create_string_buffer(self.digest_size) _digest(self._ctx, ctypes.c_size_t(self.digest_size), result) return result.raw def hexdigest(self) -> str: """Return the digest as a string of hexadecimal digits""" return binascii.b2a_hex(self.digest()).decode('ascii') return _UMAC.new for lib in ('nettle', 'libnettle', 'libnettle-6'): _nettle_lib = ctypes.util.find_library(lib) if _nettle_lib: # pragma: no branch break else: # pragma: no cover _nettle_lib = None if _nettle_lib: # pragma: no branch _nettle = ctypes.cdll.LoadLibrary(_nettle_lib) umac32, umac64, umac96, umac128 = map(_build_umac, (32, 64, 96, 128))