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
16 changes: 15 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,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 +109,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 +139,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", "./tests"]

[tool.ty.rules]
all = "warn"
1 change: 0 additions & 1 deletion tests/test_delete_descriptor_binding.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,6 @@ def recorder(*args, **kwargs):
r = Random()
native = r.native_object
r.__del__()
r.native_object = None # prevent real cleanup on the way out
assert received, "recorder was never called"
args, kwargs = received[-1]
assert kwargs == {}
Expand Down
2 changes: 1 addition & 1 deletion tests/test_mldsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ def test_sign_with_seed(mldsa_type, rng):

# test that the seed type is checked (should be bytes-like, not string)
with pytest.raises(TypeError):
_ = mldsa_priv.sign_with_seed(message, "")
_ = mldsa_priv.sign_with_seed(message, " " * ML_DSA_SIGNATURE_SEED_LENGTH)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 [Low] test_mldsa now relies on cffi's low-level TypeError instead of an explicit guard

The removed code in sign_with_seed/make_key_from_seed previously raised a clear, library-owned TypeError ('seed must support the buffer protocol...') when given a non-buffer seed. With that guard gone, the test was changed from sign_with_seed(message, "") to sign_with_seed(message, " " * ML_DSA_SIGNATURE_SEED_LENGTH) so that the length check passes and the str then reaches CFFI, which raises TypeError. The test still passes, but it now asserts on CFFI's internal type rejection ('must be a bytes or list or tuple, not str') rather than a wolfcrypt-controlled error. This couples the test to CFFI internals and produces a less user-friendly error for callers.

Fix: Optional: re-add an explicit type guard for clearer errors; otherwise acceptable as-is.


def test_sign_with_seed_and_context(mldsa_type, rng):
signature_seed = rng.bytes(ML_DSA_SIGNATURE_SEED_LENGTH)
Expand Down
5 changes: 4 additions & 1 deletion wolfcrypt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
from typing import cast

from _cffi_backend import Lib

from wolfcrypt._version import __version__, __wolfssl_version__

Expand Down Expand Up @@ -50,7 +53,7 @@

if hasattr(_lib, 'WC_RNG_SEED_CB_ENABLED'):
if _lib.WC_RNG_SEED_CB_ENABLED:
ret = _lib.wc_SetSeed_Cb(_ffi.addressof(_lib, "wc_GenerateSeed"))
ret = _lib.wc_SetSeed_Cb(_ffi.addressof(cast(Lib, _lib), "wc_GenerateSeed"))
if ret < 0:
raise WolfCryptApiError("wc_SetSeed_Cb failed", ret)
if _lib.FIPS_ENABLED and _lib.FIPS_VERSION >= 5:
Expand Down
254 changes: 254 additions & 0 deletions wolfcrypt/_ffi/lib.pyi

Large diffs are not rendered by default.

21 changes: 12 additions & 9 deletions wolfcrypt/asn.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,26 @@

# pylint: disable=no-member,no-name-in-module

from __future__ import annotations

import hmac as _hmac

from wolfcrypt._ffi import ffi as _ffi
from wolfcrypt._ffi import lib as _lib
from wolfcrypt.exceptions import WolfCryptError, WolfCryptApiError
from wolfcrypt.hashes import _Hash

if _lib.SHA_ENABLED:
from wolfcrypt.hashes import Sha
from wolfcrypt.hashes import Sha # ty: ignore[possibly-missing-import]
if _lib.SHA256_ENABLED:
from wolfcrypt.hashes import Sha256
from wolfcrypt.hashes import Sha256 # ty: ignore[possibly-missing-import]
if _lib.SHA384_ENABLED:
from wolfcrypt.hashes import Sha384
from wolfcrypt.hashes import Sha384 # ty: ignore[possibly-missing-import]
if _lib.SHA512_ENABLED:
from wolfcrypt.hashes import Sha512
from wolfcrypt.hashes import Sha512 # ty: ignore[possibly-missing-import]

if _lib.ASN_ENABLED:
def pem_to_der(pem, pem_type):
def pem_to_der(pem: bytes, pem_type: int) -> bytes:
der = _ffi.new("DerBuffer**")
ret = _lib.wc_PemToDer(pem, len(pem), pem_type, der, _ffi.NULL,
_ffi.NULL, _ffi.NULL)
Expand All @@ -49,7 +52,7 @@ def pem_to_der(pem, pem_type):
_lib.wc_FreeDer(der)
return result

def der_to_pem(der, pem_type):
def der_to_pem(der: bytes, pem_type: int) -> bytes:
pem_length = _lib.wc_DerToPemEx(der, len(der), _ffi.NULL, 0, _ffi.NULL,
pem_type)
if pem_length <= 0:
Expand All @@ -63,7 +66,7 @@ def der_to_pem(der, pem_type):

return _ffi.buffer(pem, pem_length)[:]

def hash_oid_from_class(hash_cls):
def hash_oid_from_class(hash_cls: type[_Hash]) -> int:
if _lib.SHA_ENABLED and hash_cls == Sha:
return _lib.SHAh
elif _lib.SHA256_ENABLED and hash_cls == Sha256:
Expand All @@ -75,7 +78,7 @@ def hash_oid_from_class(hash_cls):
else:
raise WolfCryptError(f"Unknown hash class {hash_cls.__name__}")

def make_signature(data, hash_cls, key=None):
def make_signature(data: bytes, hash_cls: type[_Hash], key = None) -> bytes:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 [Medium] asn.py leaves function arguments unannotated while enabling ANN ruff rules

This PR adds "ANN" to the ruff select list and only ignores it for scripts/*.py and tests/*.py (not wolfcrypt/). Yet make_signature(data, hash_cls, key = None) leaves key unannotated and check_signature(signature, data, hash_cls, pub_key) leaves pub_key unannotated. ruff's ANN001 (missing-type-function-argument) would flag both, so ruff check with the new config would report violations in a file the PR is explicitly typing. This is an internal inconsistency in the typing pass.

Fix: Complete the annotations in asn.py (and verify ruff ANN passes for wolfcrypt/) so the newly enabled rule does not fail lint.

hash_obj = hash_cls()
hash_obj.update(data)
digest = hash_obj.digest()
Expand All @@ -93,7 +96,7 @@ def make_signature(data, hash_cls, key=None):
else:
return plaintext_sig

def check_signature(signature, data, hash_cls, pub_key):
def check_signature(signature: bytes, data: bytes, hash_cls: type[_Hash], pub_key) -> bool:
computed_signature = make_signature(data, hash_cls)
decrypted_signature = pub_key.verify(signature)
return _hmac.compare_digest(computed_signature, decrypted_signature)
Loading
Loading