Add more tests.

* Add parametrized callback test for all Sha classes.
* Add callback test for unsupported algo type AES GCM Stream.
pull/114/head
Robert de Vries 2026-07-15 19:32:59 +02:00 committed by David Garske
parent d884b75c6e
commit 483b532bac
2 changed files with 60 additions and 16 deletions

View File

@ -22,27 +22,49 @@
from __future__ import annotations
import struct
from binascii import hexlify as b2h
import pytest
from typing_extensions import override
from wolfcrypt.exceptions import WolfCryptApiError
from wolfcrypt._ffi import lib as _lib
from wolfcrypt.exceptions import WolfCryptApiError
from wolfcrypt.hashes import _Sha
from wolfcrypt.random import Random
if _lib.SHA_ENABLED:
from wolfcrypt.hashes import Sha
if not _lib.CRYPTO_CB_ENABLED:
pytest.skip("Crypto Callbacks not supported", allow_module_level=True)
from wolfcrypt.cryptocb import CryptoCallback
from wolfcrypt.cryptocb import CryptoCallback, DIGEST_SIZE
SHA_CLASSES: list[type[_Sha]] = []
if _lib.SHA_ENABLED:
from wolfcrypt.hashes import Sha
SHA_CLASSES.append(Sha)
if _lib.SHA256_ENABLED:
from wolfcrypt.hashes import Sha256
SHA_CLASSES.append(Sha256)
if _lib.SHA384_ENABLED:
from wolfcrypt.hashes import Sha384
SHA_CLASSES.append(Sha384)
if _lib.SHA512_ENABLED:
from wolfcrypt.hashes import Sha512
SHA_CLASSES.append(Sha512)
if _lib.SHA3_ENABLED:
from wolfcrypt.hashes import Sha3
SHA_CLASSES.append(Sha3)
def test_default_device_id():
# In the python implementation the default device ID is the invalid device ID.
assert CryptoCallback.default_device_id() == _lib.INVALID_DEVID
class RngCryptoCallback(CryptoCallback):
@override
def rng_callback(self, device_id: int, rng: _lib.RNG, size: int) -> bytes:
@ -63,6 +85,7 @@ def test_rng_callback():
random = rng.bytes(3)
assert random == b"\01\02\03"
class HashCryptoCallback(CryptoCallback):
def __init__(self, device_id):
super().__init__(device_id)
@ -75,23 +98,27 @@ class HashCryptoCallback(CryptoCallback):
@override
def hash_finalize_callback(self, device_id: int, hash_type: int) -> bytes:
# quite lame hash function, just returns the length of the data as an integer (padded to match the expected hash length).
return struct.pack("I16x", len(b"".join(self.data)))
return struct.pack(f"I{DIGEST_SIZE[hash_type] - 4}x", len(b"".join(self.data)))
if _lib.SHA_ENABLED:
def test_hash_callback():
with HashCryptoCallback(11):
sha = Sha(device_id=11)
sha.update(bytes(10))
sha.update(bytes(5))
digest = sha.digest()
assert digest == struct.pack("I16x", 15)
@pytest.mark.parametrize("sha_cls", SHA_CLASSES)
def test_hash_callback(sha_cls: type[_Sha]) -> None:
with HashCryptoCallback(11):
sha = sha_cls(device_id=11)
sha.update(bytes(10))
sha.update(bytes(5))
digest = sha.digest()
digest_size = sha.digest_size
assert digest_size is not None
assert digest == struct.pack(f"I{digest_size - 4}x", 15)
class BadHashCryptoCallback(CryptoCallback):
@override
def hash_finalize_callback(self, device_id: int, hash_type: int) -> bytes:
return bytes(1) # bad hash length
if _lib.SHA_ENABLED:
def test_hash_callback_failure():
with BadHashCryptoCallback(12):
@ -99,3 +126,20 @@ if _lib.SHA_ENABLED:
sha.update(bytes(10))
with pytest.raises(WolfCryptApiError):
sha.digest()
if _lib.AESGCM_STREAM_ENABLED:
from wolfcrypt.ciphers import AesGcmStream
def test_crypto_callback_aes_gcm_stream_encrypt():
"""Verify that the crypto callback that does not support AesGcmStream works as if not callback was installed."""
with CryptoCallback(13):
"""Known answer encrypt-decrypt test with default authentication tag size of 16 bytes"""
key = "fedcba9876543210"
iv = "0123456789abcdef"
gcm = AesGcmStream(key, iv, device_id=13)
buf = gcm.encrypt("hello world")
authTag = gcm.final()
assert authTag is not None
assert b2h(authTag) == bytes('ac8fcee96dc6ef8e5236da19b6197d2e', 'utf-8')
assert b2h(buf) == bytes('5ba7d42e1bf01d7998e932', "utf-8")

View File

@ -437,7 +437,7 @@ if _lib.AESGCM_STREAM_ENABLED:
# making sure _lib.wc_AesFree outlives Aes instances
_delete = staticmethod(_lib.wc_AesFree)
def __init__(self, key: BytesOrStr, IV: BytesOrStr, tag_bytes: int = 16) -> None:
def __init__(self, key: BytesOrStr, IV: BytesOrStr, tag_bytes: int = 16, device_id: int = _lib.INVALID_DEVID) -> None:
"""
tag_bytes is the number of bytes to use for the authentication tag during encryption
"""
@ -460,7 +460,7 @@ if _lib.AESGCM_STREAM_ENABLED:
raise ValueError(f"key must be {self._key_sizes} in length, not {len(key)}")
self._init_done = False
self._native_object = _ffi.new(self._native_type)
ret = _lib.wc_AesInit(self._native_object, _ffi.NULL, _lib.INVALID_DEVID)
ret = _lib.wc_AesInit(self._native_object, _ffi.NULL, device_id)
if ret < 0:
raise WolfCryptApiError("AES init error", ret)
self._init_done = True