Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 2 additions & 0 deletions ChangeLog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ wolfCrypt-py Release 5.9.2 (Jul 1, 2026)
* Fix issue in AES-GCM tag verification
* Address many small issues found by Fenrir
* Add reseed support to random number generator
* The RsaPublic key parameter is now mandatory as it is always needed by an internal function call.
* Add typing annotations


wolfCrypt-py Release 5.8.4 (Jan 7, 2026)
Expand Down
17 changes: 16 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ classifiers = [
dynamic = ["version"]
dependencies = [
"cffi>=1.17",
"typing-extensions>=4.4.0",
]

[project.urls]
Expand Down Expand Up @@ -99,7 +100,7 @@ target-version = "py310"
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
# McCabe complexity (`C901`) by default.
select = ["E4", "E7", "E9", "F", "B", "UP"]
select = ["E4", "E7", "E9", "F", "B", "UP", "ANN"]
ignore = ["UP031", "UP025", "UP032"]

# Allow fix for all enabled rules (when `--fix`) is provided.
Expand All @@ -109,6 +110,10 @@ unfixable = []
# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"

[tool.ruff.lint.per-file-ignores]
"scripts/*.py" = ["ANN"]
"tests/*.py" = ["ANN"]

[tool.ruff.format]
# Like Black, use double quotes for strings.
quote-style = "double"
Expand All @@ -135,3 +140,13 @@ docstring-code-format = false
# This only has an effect when the `docstring-code-format` setting is
# enabled.
docstring-code-line-length = "dynamic"

[tool.ty.environment]
python-version = "3.10"
root = ["."]

[tool.ty.src]
exclude = ["./lib"]

[tool.ty.rules]
all = "warn"
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,5 @@
install_requires=["cffi>=1.17"],
cffi_modules=["./scripts/build_ffi.py:ffibuilder"],

package_data={"wolfcrypt": ["*.dll", "**/*.pyi"]}
package_data={"wolfcrypt": ["*.dll", "**/*.pyi", "py.typed"]},
)
9 changes: 9 additions & 0 deletions tests/test_aesgcmstream.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA

# pylint: disable=redefined-outer-name
# ty: ignore[possibly-missing-import]

from contextlib import nullcontext

Expand All @@ -38,6 +39,7 @@ def test_encrypt():
gcm = AesGcmStream(key, iv)
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")
gcmdec = AesGcmStream(key, iv)
Expand All @@ -53,6 +55,7 @@ def test_encrypt_short_tag():
gcm = AesGcmStream(key, iv, 12)
buf = gcm.encrypt("hello world")
authTag = gcm.final()
assert authTag is not None
assert b2h(authTag) == bytes('ac8fcee96dc6ef8e5236da19', 'utf-8')
assert b2h(buf) == bytes('5ba7d42e1bf01d7998e932', "utf-8")
gcmdec = AesGcmStream(key, iv, 12)
Expand All @@ -67,6 +70,7 @@ def test_multipart():
buf = gcm.encrypt("hello")
buf += gcm.encrypt(" world")
authTag = gcm.final()
assert authTag is not None
assert b2h(authTag) == bytes('ac8fcee96dc6ef8e5236da19b6197d2e', 'utf-8')
assert b2h(buf) == bytes('5ba7d42e1bf01d7998e932', "utf-8")
gcmdec = AesGcmStream(key, iv)
Expand All @@ -83,6 +87,7 @@ def test_encrypt_aad():
gcm.set_aad(aad)
buf = gcm.encrypt("hello world")
authTag = gcm.final()
assert authTag is not None
print(b2h(authTag))
assert b2h(authTag) == bytes('8f85338aa0b13f48f8b17482dbb8acca', 'utf-8')
assert b2h(buf) == bytes('5ba7d42e1bf01d7998e932', "utf-8")
Expand All @@ -101,6 +106,7 @@ def test_multipart_aad():
buf = gcm.encrypt("hello")
buf += gcm.encrypt(" world")
authTag = gcm.final()
assert authTag is not None
assert b2h(authTag) == bytes('8f85338aa0b13f48f8b17482dbb8acca', 'utf-8')
assert b2h(buf) == bytes('5ba7d42e1bf01d7998e932', "utf-8")
gcmdec = AesGcmStream(key, iv)
Expand All @@ -119,6 +125,7 @@ def test_encrypt_aad_bad():
gcm.set_aad(aad)
buf = gcm.encrypt("hello world")
authTag = gcm.final()
assert authTag is not None
print(b2h(authTag))
assert b2h(authTag) == bytes('8f85338aa0b13f48f8b17482dbb8acca', 'utf-8')
assert b2h(buf) == bytes('5ba7d42e1bf01d7998e932', "utf-8")
Expand Down Expand Up @@ -152,6 +159,7 @@ def test_invalid_tag_bytes():
gcm = AesGcmStream(key, iv, tag_bytes=good)
gcm.encrypt("hello world")
tag = gcm.final()
assert tag is not None
assert len(tag) == good

def test_decrypt_rejects_wrong_tag_length():
Expand All @@ -160,6 +168,7 @@ def test_decrypt_rejects_wrong_tag_length():
gcm = AesGcmStream(key, iv, tag_bytes=16)
buf = gcm.encrypt("hello world")
authTag = gcm.final()
assert authTag is not None
assert len(authTag) == 16

# Truncated tag: would silently lower the verification window to
Expand Down
5 changes: 3 additions & 2 deletions tests/test_asn.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA

# pylint: disable=redefined-outer-name
# ty: ignore[possibly-missing-import]

from collections import namedtuple
import pytest
Expand Down Expand Up @@ -90,8 +91,8 @@ def signature_vectors():
"51a4edaf9f1199f93e448482f27c43a53e0bc65b04e9848128e3"
"60314e864190e6bb9812bfbf4b40994f2c1d4ca7aad9"),
hash_cls=Sha256,
pub_key=RsaPublic.from_pem(pub_key_pem),
priv_key=RsaPrivate.from_pem(priv_key_pem)
pub_key=RsaPublic.from_pem(pub_key_pem), # ty: ignore[possibly-missing-attribute]
priv_key=RsaPrivate.from_pem(priv_key_pem) # ty: ignore[possibly-missing-attribute]
))

return vectors
Expand Down
2 changes: 1 addition & 1 deletion tests/test_chacha20poly1305.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from wolfcrypt.utils import t2b
from wolfcrypt.exceptions import WolfCryptError
from binascii import unhexlify as h2b
from wolfcrypt.ciphers import ChaCha20Poly1305
from wolfcrypt.ciphers import ChaCha20Poly1305 # ty: ignore[possibly-missing-import]

def test_encrypt_decrypt():
key = h2b("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f")
Expand Down
13 changes: 3 additions & 10 deletions tests/test_chacha_iv.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
#
# test_chacha_iv.py
#
# Copyright (C) 2006-2022 wolfSSL Inc.
Expand All @@ -26,6 +24,9 @@
from wolfcrypt._ffi import lib as _lib
from wolfcrypt.exceptions import WolfCryptError

if _lib.CHACHA_ENABLED:
from wolfcrypt.ciphers import ChaCha # ty: ignore[possibly-missing-import]

pytestmark = pytest.mark.skipif(
not _lib.CHACHA_ENABLED, reason="ChaCha not enabled")

Expand All @@ -38,24 +39,18 @@ def test_encrypt_before_set_iv_raises():
F-4463: encrypt() before set_iv() must not feed an empty IV buffer to
wc_Chacha_SetIV (which unconditionally reads 12 bytes). It must raise.
"""
from wolfcrypt.ciphers import ChaCha

cipher = ChaCha(KEY)
with pytest.raises(WolfCryptError):
cipher.encrypt(b"A" * 16)


def test_decrypt_before_set_iv_raises():
from wolfcrypt.ciphers import ChaCha

cipher = ChaCha(KEY)
with pytest.raises(WolfCryptError):
cipher.decrypt(b"A" * 16)


def test_encrypt_decrypt_after_set_iv_roundtrips():
from wolfcrypt.ciphers import ChaCha

enc = ChaCha(KEY)
enc.set_iv(NONCE)
plaintext = b"the quick brown fox"
Expand All @@ -72,8 +67,6 @@ def test_failed_set_iv_keeps_encrypt_blocked(monkeypatch):
encrypt()/decrypt() stay blocked rather than running with a stale or
partially-applied IV.
"""
from wolfcrypt.ciphers import ChaCha

cipher = ChaCha(KEY)
# First, establish a valid IV so a later failure would otherwise leave
# _iv_set True under the old ordering.
Expand Down
4 changes: 1 addition & 3 deletions tests/test_cipher_modes.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
#
# test_cipher_modes.py
#
# Copyright (C) 2006-2022 wolfSSL Inc.
Expand Down Expand Up @@ -49,7 +47,7 @@ def test_unsupported_mode_gives_single_consistent_error():
then hit a contradictory "not supported by this cipher" branch. The
rejection must now be a single, consistent message.
"""
from wolfcrypt.ciphers import Aes
from wolfcrypt.ciphers import Aes # ty: ignore[possibly-missing-import]

key = b"0" * 16
iv = b"0" * 16
Expand Down
50 changes: 24 additions & 26 deletions tests/test_ciphers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA

# pylint: disable=redefined-outer-name
# ty: ignore[possibly-missing-import]

import os
import random
Expand Down Expand Up @@ -60,10 +61,8 @@

@pytest.fixture
def vectors():
TestVector = namedtuple("TestVector", """key iv plaintext ciphertext
ciphertext_ctr raw_key
pkcs8_key pem""")
TestVector.__new__.__defaults__ = (None,) * len(TestVector._fields)
fields = ("key", "iv", "plaintext", "ciphertext", "ciphertext_ctr", "raw_key", "pkcs8_key", "pem")
TestVector = namedtuple("TestVector", fields, defaults=(None,) * len(fields))

# test vector dictionary
vectorArray = {}
Expand All @@ -75,19 +74,19 @@ def vectors():
plaintext=t2b("now is the time "),
ciphertext=h2b("959492575f4281532ccc9d4677a233cb"),
ciphertext_ctr = h2b('287528ddf484b1055debbe751eb52b8a')
)
) # ty: ignore[missing-argument]
if _lib.CHACHA_ENABLED:
vectorArray[ChaCha]=TestVector(
key="0123456789abcdef01234567890abcdef",
iv="1234567890abcdef",
)
key="0123456789abcdef0123456789abcdef",
iv="1234567890ab",
) # ty: ignore[missing-argument]
if _lib.DES3_ENABLED:
vectorArray[Des3]=TestVector(
key=h2b("0123456789abcdeffedeba987654321089abcdef01234567"),
iv=h2b("1234567890abcdef"),
plaintext=t2b("Now is the time for all "),
ciphertext=h2b("43a0297ed184f80e8964843212d508981894157487127db0")
)
) # ty: ignore[missing-argument]
if _lib.RSA_ENABLED:
vectorArray[RsaPublic]=TestVector(
key=h2b(
Expand All @@ -98,7 +97,7 @@ def vectors():
"0E22E96BA426BA4CE8C1FD4A6F2B1FEF8AAEF69062E5641EEB2B3C67C8DC"
"2700F6916865A90203010001"),
pem=os.path.join(certs_dir, "server-keyPub.pem")
)
) # ty: ignore[missing-argument]
vectorArray[RsaPrivate]=TestVector(
key=h2b(
"3082025C02010002818100BC730EA849F374A2A9EF18A5DA559921F9C8EC"
Expand Down Expand Up @@ -164,7 +163,7 @@ def vectors():
"1666d37c742b15b4a2febf086b1a5d3f"
"9012b105863129dbd9e2"),
pem=os.path.join(certs_dir, "server-key.pem")
)
) # ty: ignore[missing-argument]

if _lib.ECC_ENABLED:
vectorArray[EccPublic]=TestVector(
Expand All @@ -178,7 +177,7 @@ def vectors():
"55bff40f44509a3dce9bb7f0c54df5707bd4ec248e1980ec5a4ca22403622c9b"
"daefa2351243847616c6569506cc01a9bdf6751a42f7bda9b236225fc75d7fb4"
)
)
) # ty: ignore[missing-argument]
vectorArray[EccPrivate]=TestVector(
key=h2b(
"30770201010420F8CF926BBD1E28F1A8ABA1234F3274188850AD7EC7EC92"
Expand All @@ -192,41 +191,41 @@ def vectors():
"daefa2351243847616c6569506cc01a9bdf6751a42f7bda9b236225fc75d7fb4"
"f8cf926bbd1e28f1a8aba1234f3274188850ad7ec7ec92f88f974daf568965c7"
)
)
) # ty: ignore[missing-argument]

if _lib.ED25519_ENABLED:
vectorArray[Ed25519Private]=TestVector(
key = h2b(
"47CD22B276161AA18BA1E0D13DBE84FE4840E4395D784F555A92E8CF739B"
"F86B"
)
)
) # ty: ignore[missing-argument]
vectorArray[Ed25519Public]=TestVector(
key=h2b(
"8498C65F4841145F9C51E8BFF4504B5527E0D5753964B7CB3C707A2B9747"
"FC96"
)
)
) # ty: ignore[missing-argument]
if _lib.ED448_ENABLED:
vectorArray[Ed448Private]=TestVector(
key=h2b("c2b29804e9a893c9e275cac1f8a3033f3d4b78b79eb427ed359fdeb8"
"82d657c129c7930936b181971b795167ad18cabeeb52b59b94f115ad"
"59"
)
)
) # ty: ignore[missing-argument]
vectorArray[Ed448Public]=TestVector(
key=h2b("89fb2b5a5ab67dd317794cc5f1700cace295b043f3ad73a66299e10a"
"d3fc0a28289ddd1c641598a354113867a42e82ad844b4d858d92e4e7"
"80"
)
)
) # ty: ignore[missing-argument]
return vectorArray

algo_params = []
if _lib.AES_ENABLED:
algo_params.append(Aes)
algo_params.append(Aes) # ty: ignore[possibly-unresolved-reference]
if _lib.DES3_ENABLED:
algo_params.append(Des3)
algo_params.append(Des3) # ty: ignore[possibly-unresolved-reference]

@pytest.fixture(params=algo_params)
def cipher_cls(request):
Expand Down Expand Up @@ -319,8 +318,7 @@ def chacha_obj(vectors):
r.set_iv(vectors[ChaCha].iv)
return r

@pytest.fixture
def test_chacha_enc_dec(chacha_obj):
def test_chacha_enc_dec(chacha_obj, vectors):
plaintext = t2b("Everyone gets Friday off.")
cyt = chacha_obj.encrypt(plaintext)
chacha_obj.set_iv(vectors[ChaCha].iv)
Expand Down Expand Up @@ -372,25 +370,25 @@ def rsa_public_pss(vectors):
def rsa_private_pem(vectors):
with open(vectors[RsaPrivate].pem, "rb") as f:
pem = f.read()
return RsaPrivate.from_pem(pem)
return RsaPrivate.from_pem(pem) # ty: ignore[possibly-missing-attribute]

@pytest.fixture
def rsa_public_pem(vectors):
with open(vectors[RsaPublic].pem, "rb") as f:
pem = f.read()
return RsaPublic.from_pem(pem)
return RsaPublic.from_pem(pem) # ty: ignore[possibly-missing-attribute]

@pytest.fixture
def rsa_private_pem_rng(vectors, rng):
with open(vectors[RsaPrivate].pem, "rb") as f:
pem = f.read()
return RsaPrivate.from_pem(pem, rng=rng)
return RsaPrivate.from_pem(pem, rng=rng) # ty: ignore[possibly-missing-attribute]

@pytest.fixture
def rsa_public_pem_rng(vectors, rng):
with open(vectors[RsaPublic].pem, "rb") as f:
pem = f.read()
return RsaPublic.from_pem(pem, rng=rng)
return RsaPublic.from_pem(pem, rng=rng) # ty: ignore[possibly-missing-attribute]

def test_new_rsa_raises(vectors):
with pytest.raises(WolfCryptError):
Expand All @@ -401,7 +399,7 @@ def test_new_rsa_raises(vectors):

if _lib.KEYGEN_ENABLED:
with pytest.raises(WolfCryptError): # invalid key size
RsaPrivate.make_key(16384)
RsaPrivate.make_key(16384) # ty: ignore[possibly-missing-attribute]


def test_rsa_encrypt_decrypt(rsa_private, rsa_public):
Expand Down
Loading
Loading