From 747243cc503808844305a5ddc4e1d4bd67f0114b Mon Sep 17 00:00:00 2001 From: Robert de Vries Date: Thu, 11 Jun 2026 22:42:55 +0200 Subject: [PATCH] Address more review comments. Tests are now also type checked as this helps verifying the correctness of the type annotations. --- ChangeLog.rst | 1 + pyproject.toml | 3 +- tests/test_aesgcmstream.py | 9 + tests/test_asn.py | 5 +- tests/test_chacha20poly1305.py | 2 +- tests/test_ciphers.py | 45 +++-- tests/test_delete_descriptor_binding.py | 20 +- tests/test_hashes.py | 19 +- tests/test_hkdf.py | 1 + tests/test_mldsa.py | 3 +- tests/test_mlkem.py | 1 + tests/test_pwdbased.py | 2 + wolfcrypt/_ffi/__init__.pyi | 20 ++ wolfcrypt/_ffi/lib.pyi | 237 ++++++++++++++++++++++++ wolfcrypt/asn.py | 5 +- wolfcrypt/ciphers.py | 21 ++- wolfcrypt/hkdf.py | 6 +- wolfcrypt/types.py | 35 ++++ 18 files changed, 376 insertions(+), 59 deletions(-) create mode 100644 wolfcrypt/types.py diff --git a/ChangeLog.rst b/ChangeLog.rst index 06afabf..06c5ed8 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -27,6 +27,7 @@ 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. wolfCrypt-py Release 5.8.4 (Jan 7, 2026) diff --git a/pyproject.toml b/pyproject.toml index 6ab85f4..d13041e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ classifiers = [ dynamic = ["version"] dependencies = [ "cffi>=1.17", + "typing-extensions", ] [project.urls] @@ -143,7 +144,7 @@ python-version = "3.10" root = ["."] [tool.ty.src] -exclude = ["./lib", "./tests"] +exclude = ["./lib"] [tool.ty.rules] all = "warn" diff --git a/tests/test_aesgcmstream.py b/tests/test_aesgcmstream.py index d58b427..7f22861 100644 --- a/tests/test_aesgcmstream.py +++ b/tests/test_aesgcmstream.py @@ -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 @@ -38,6 +39,7 @@ if _lib.AESGCM_STREAM_ENABLED: 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) @@ -53,6 +55,7 @@ if _lib.AESGCM_STREAM_ENABLED: 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) @@ -67,6 +70,7 @@ if _lib.AESGCM_STREAM_ENABLED: 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) @@ -83,6 +87,7 @@ if _lib.AESGCM_STREAM_ENABLED: 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") @@ -101,6 +106,7 @@ if _lib.AESGCM_STREAM_ENABLED: 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) @@ -119,6 +125,7 @@ if _lib.AESGCM_STREAM_ENABLED: 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") @@ -152,6 +159,7 @@ if _lib.AESGCM_STREAM_ENABLED: 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(): @@ -160,6 +168,7 @@ if _lib.AESGCM_STREAM_ENABLED: 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 diff --git a/tests/test_asn.py b/tests/test_asn.py index a26b853..6ef4f6e 100644 --- a/tests/test_asn.py +++ b/tests/test_asn.py @@ -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 @@ -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 diff --git a/tests/test_chacha20poly1305.py b/tests/test_chacha20poly1305.py index 07616ea..fe691ce 100644 --- a/tests/test_chacha20poly1305.py +++ b/tests/test_chacha20poly1305.py @@ -28,7 +28,7 @@ if _lib.CHACHA20_POLY1305_ENABLED: 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") diff --git a/tests/test_ciphers.py b/tests/test_ciphers.py index 9e4b3f2..e04dd9c 100644 --- a/tests/test_ciphers.py +++ b/tests/test_ciphers.py @@ -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 @@ -60,10 +61,8 @@ if _lib.ED448_ENABLED: @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 = {} @@ -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", - ) + ) # 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( @@ -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" @@ -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( @@ -178,7 +177,7 @@ def vectors(): "55bff40f44509a3dce9bb7f0c54df5707bd4ec248e1980ec5a4ca22403622c9b" "daefa2351243847616c6569506cc01a9bdf6751a42f7bda9b236225fc75d7fb4" ) - ) + ) # ty: ignore[missing-argument] vectorArray[EccPrivate]=TestVector( key=h2b( "30770201010420F8CF926BBD1E28F1A8ABA1234F3274188850AD7EC7EC92" @@ -192,7 +191,7 @@ def vectors(): "daefa2351243847616c6569506cc01a9bdf6751a42f7bda9b236225fc75d7fb4" "f8cf926bbd1e28f1a8aba1234f3274188850ad7ec7ec92f88f974daf568965c7" ) - ) + ) # ty: ignore[missing-argument] if _lib.ED25519_ENABLED: vectorArray[Ed25519Private]=TestVector( @@ -200,33 +199,33 @@ def vectors(): "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): @@ -320,7 +319,7 @@ if _lib.CHACHA_ENABLED: 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) @@ -372,25 +371,25 @@ if _lib.RSA_ENABLED: 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): @@ -401,7 +400,7 @@ if _lib.RSA_ENABLED: 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): diff --git a/tests/test_delete_descriptor_binding.py b/tests/test_delete_descriptor_binding.py index 6f96616..862fa1f 100644 --- a/tests/test_delete_descriptor_binding.py +++ b/tests/test_delete_descriptor_binding.py @@ -55,39 +55,39 @@ def _static_attrs(): yield Random, "_delete" if _lib.SHA_ENABLED: - from wolfcrypt.hashes import Sha + from wolfcrypt.hashes import Sha # ty: ignore[possibly-missing-import] yield Sha, "_delete" yield Sha, "_copy" if _lib.SHA256_ENABLED: - from wolfcrypt.hashes import Sha256 + from wolfcrypt.hashes import Sha256 # ty: ignore[possibly-missing-import] yield Sha256, "_delete" yield Sha256, "_copy" if _lib.SHA384_ENABLED: - from wolfcrypt.hashes import Sha384 + from wolfcrypt.hashes import Sha384 # ty: ignore[possibly-missing-import] yield Sha384, "_delete" yield Sha384, "_copy" if _lib.SHA512_ENABLED: - from wolfcrypt.hashes import Sha512 + from wolfcrypt.hashes import Sha512 # ty: ignore[possibly-missing-import] yield Sha512, "_delete" yield Sha512, "_copy" if _lib.HMAC_ENABLED: - from wolfcrypt.hashes import _Hmac + from wolfcrypt.hashes import _Hmac # ty: ignore[possibly-missing-import] yield _Hmac, "_delete" if _lib.AESGCM_STREAM_ENABLED: - from wolfcrypt.ciphers import AesGcmStream + from wolfcrypt.ciphers import AesGcmStream # ty: ignore[possibly-missing-import] yield AesGcmStream, "_delete" if _lib.RSA_ENABLED: - from wolfcrypt.ciphers import _Rsa + from wolfcrypt.ciphers import _Rsa # ty: ignore[possibly-missing-import] yield _Rsa, "_delete" if _lib.ECC_ENABLED: - from wolfcrypt.ciphers import _Ecc + from wolfcrypt.ciphers import _Ecc # ty: ignore[possibly-missing-import] yield _Ecc, "_delete" if _lib.ED25519_ENABLED: - from wolfcrypt.ciphers import _Ed25519 + from wolfcrypt.ciphers import _Ed25519 # ty: ignore[possibly-missing-import] yield _Ed25519, "_delete" if _lib.ED448_ENABLED: - from wolfcrypt.ciphers import _Ed448 + from wolfcrypt.ciphers import _Ed448 # ty: ignore[possibly-missing-import] yield _Ed448, "_delete" diff --git a/tests/test_hashes.py b/tests/test_hashes.py index 482e51c..5510cbc 100644 --- a/tests/test_hashes.py +++ b/tests/test_hashes.py @@ -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 @@ -116,26 +117,26 @@ def vectors(): hash_params = [] if _lib.SHA_ENABLED: - hash_params.append(Sha) + hash_params.append(Sha) # ty: ignore[possibly-unresolved-reference] if _lib.SHA256_ENABLED: - hash_params.append(Sha256) + hash_params.append(Sha256) # ty: ignore[possibly-unresolved-reference] if _lib.SHA384_ENABLED: - hash_params.append(Sha384) + hash_params.append(Sha384) # ty: ignore[possibly-unresolved-reference] if _lib.SHA512_ENABLED: - hash_params.append(Sha512) + hash_params.append(Sha512) # ty: ignore[possibly-unresolved-reference] if _lib.SHA3_ENABLED: - hash_params.append(Sha3) + hash_params.append(Sha3) # ty: ignore[possibly-unresolved-reference] hmac_params = [] if _lib.HMAC_ENABLED: if _lib.SHA_ENABLED: - hmac_params.append(HmacSha) + hmac_params.append(HmacSha) # ty: ignore[possibly-unresolved-reference] if _lib.SHA256_ENABLED: - hmac_params.append(HmacSha256) + hmac_params.append(HmacSha256) # ty: ignore[possibly-unresolved-reference] if _lib.SHA384_ENABLED: - hmac_params.append(HmacSha384) + hmac_params.append(HmacSha384) # ty: ignore[possibly-unresolved-reference] if _lib.SHA512_ENABLED: - hmac_params.append(HmacSha512) + hmac_params.append(HmacSha512) # ty: ignore[possibly-unresolved-reference] @pytest.fixture(params=(hash_params + hmac_params)) def hash_cls(request): diff --git a/tests/test_hkdf.py b/tests/test_hkdf.py index 7c215c9..26ecb12 100644 --- a/tests/test_hkdf.py +++ b/tests/test_hkdf.py @@ -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 pytest diff --git a/tests/test_mldsa.py b/tests/test_mldsa.py index f917310..7a24ab4 100644 --- a/tests/test_mldsa.py +++ b/tests/test_mldsa.py @@ -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 wolfcrypt._ffi import lib as _lib @@ -202,7 +203,7 @@ if _lib.ML_DSA_ENABLED: # test that the seed type is checked (should be bytes-like, not string) with pytest.raises(TypeError): - _ = mldsa_priv.sign_with_seed(message, " " * ML_DSA_SIGNATURE_SEED_LENGTH) + _ = mldsa_priv.sign_with_seed(message, "") # ty: ignore[invalid-argument-type] def test_sign_with_seed_and_context(mldsa_type, rng): signature_seed = rng.bytes(ML_DSA_SIGNATURE_SEED_LENGTH) diff --git a/tests/test_mlkem.py b/tests/test_mlkem.py index c84af8d..515b2f6 100644 --- a/tests/test_mlkem.py +++ b/tests/test_mlkem.py @@ -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 wolfcrypt._ffi import lib as _lib diff --git a/tests/test_pwdbased.py b/tests/test_pwdbased.py index 0d2d1ca..18a07ab 100644 --- a/tests/test_pwdbased.py +++ b/tests/test_pwdbased.py @@ -19,6 +19,8 @@ # 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 from wolfcrypt._ffi import lib as _lib diff --git a/wolfcrypt/_ffi/__init__.pyi b/wolfcrypt/_ffi/__init__.pyi index f3a1ba1..6617e32 100644 --- a/wolfcrypt/_ffi/__init__.pyi +++ b/wolfcrypt/_ffi/__init__.pyi @@ -1,3 +1,23 @@ +# __init__.pyi +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfSSL. (formerly known as CyaSSL) +# +# wolfSSL is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# wolfSSL is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 + import _cffi_backend import wolfcrypt._ffi.lib as lib diff --git a/wolfcrypt/_ffi/lib.pyi b/wolfcrypt/_ffi/lib.pyi index b44957b..b104955 100644 --- a/wolfcrypt/_ffi/lib.pyi +++ b/wolfcrypt/_ffi/lib.pyi @@ -1,3 +1,22 @@ +# lib.pyi +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfSSL. (formerly known as CyaSSL) +# +# wolfSSL is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# wolfSSL is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 _cffi_backend import FFI from typing import TypeAlias @@ -34,6 +53,224 @@ SHA384_ENABLED: int SHA512_ENABLED: int WC_RNG_SEED_CB_ENABLED: int +# Error codes +WC_FAILURE: int +MAX_CODE_E: int +WC_FIRST_E: int +WC_SPAN1_FIRST_E: int +MP_MEM: int +MP_VAL: int +MP_WOULDBLOCK: int +MP_NOT_INF: int +OPEN_RAN_E: int +READ_RAN_E: int +WINCRYPT_E: int +CRYPTGEN_E: int +RAN_BLOCK_E: int +BAD_MUTEX_E: int +WC_TIMEOUT_E: int +WC_PENDING_E: int +WC_NO_PENDING_E: int +MP_INIT_E: int +MP_READ_E: int +MP_EXPTMOD_E: int +MP_TO_E: int +MP_SUB_E: int +MP_ADD_E: int +MP_MUL_E: int +MP_MULMOD_E: int +MP_MOD_E: int +MP_INVMOD_E: int +MP_CMP_E: int +MP_ZERO_E: int +AES_EAX_AUTH_E: int +KEY_EXHAUSTED_E: int +MEMORY_E: int +VAR_STATE_CHANGE_E: int +FIPS_DEGRADED_E: int +FIPS_CODE_SZ_E: int +FIPS_DATA_SZ_E: int +RSA_WRONG_TYPE_E: int +RSA_BUFFER_E: int +BUFFER_E: int +ALGO_ID_E: int +PUBLIC_KEY_E: int +DATE_E: int +SUBJECT_E: int +ISSUER_E: int +CA_TRUE_E: int +EXTENSIONS_E: int +ASN_PARSE_E: int +ASN_VERSION_E: int +ASN_GETINT_E: int +ASN_RSA_KEY_E: int +ASN_OBJECT_ID_E: int +ASN_TAG_NULL_E: int +ASN_EXPECT_0_E: int +ASN_BITSTR_E: int +ASN_UNKNOWN_OID_E: int +ASN_DATE_SZ_E: int +ASN_BEFORE_DATE_E: int +ASN_AFTER_DATE_E: int +ASN_SIG_OID_E: int +ASN_TIME_E: int +ASN_INPUT_E: int +ASN_SIG_CONFIRM_E: int +ASN_SIG_HASH_E: int +ASN_SIG_KEY_E: int +ASN_DH_KEY_E: int +KDF_SRTP_KAT_FIPS_E: int +ASN_CRIT_EXT_E: int +ASN_ALT_NAME_E: int +ASN_NO_PEM_HEADER: int +ED25519_KAT_FIPS_E: int +ED448_KAT_FIPS_E: int +PBKDF2_KAT_FIPS_E: int +WC_KEY_MISMATCH_E: int +ECC_BAD_ARG_E: int +ASN_ECC_KEY_E: int +ECC_CURVE_OID_E: int +BAD_FUNC_ARG: int +NOT_COMPILED_IN: int +UNICODE_SIZE_E: int +NO_PASSWORD: int +ALT_NAME_E: int +BAD_OCSP_RESPONDER: int +CRL_CERT_DATE_ERR: int +AES_GCM_AUTH_E: int +AES_CCM_AUTH_E: int +ASYNC_INIT_E: int +COMPRESS_INIT_E: int +COMPRESS_E: int +DECOMPRESS_INIT_E: int +DECOMPRESS_E: int +BAD_ALIGN_E: int +ASN_NO_SIGNER_E: int +ASN_CRL_CONFIRM_E: int +ASN_CRL_NO_SIGNER_E: int +ASN_OCSP_CONFIRM_E: int +BAD_STATE_E: int +BAD_PADDING_E: int +REQ_ATTRIBUTE_E: int +PKCS7_OID_E: int +PKCS7_RECIP_E: int +FIPS_NOT_ALLOWED_E: int +ASN_NAME_INVALID_E: int +RNG_FAILURE_E: int +HMAC_MIN_KEYLEN_E: int +RSA_PAD_E: int +LENGTH_ONLY_E: int +IN_CORE_FIPS_E: int +AES_KAT_FIPS_E: int +DES3_KAT_FIPS_E: int +HMAC_KAT_FIPS_E: int +RSA_KAT_FIPS_E: int +DRBG_KAT_FIPS_E: int +DRBG_CONT_FIPS_E: int +AESGCM_KAT_FIPS_E: int +THREAD_STORE_KEY_E: int +THREAD_STORE_SET_E: int +MAC_CMP_FAILED_E: int +IS_POINT_E: int +ECC_INF_E: int +ECC_PRIV_KEY_E: int +ECC_OUT_OF_RANGE_E: int +SRP_CALL_ORDER_E: int +SRP_VERIFY_E: int +SRP_BAD_KEY_E: int +ASN_NO_SKID: int +ASN_NO_AKID: int +ASN_NO_KEYUSAGE: int +SKID_E: int +AKID_E: int +KEYUSAGE_E: int +CERTPOLICIES_E: int +WC_INIT_E: int +SIG_VERIFY_E: int +BAD_COND_E: int +SIG_TYPE_E: int +HASH_TYPE_E: int +FIPS_INVALID_VER_E: int +WC_KEY_SIZE_E: int +ASN_COUNTRY_SIZE_E: int +MISSING_RNG_E: int +ASN_PATHLEN_SIZE_E: int +ASN_PATHLEN_INV_E: int +BAD_KEYWRAP_ALG_E: int +BAD_KEYWRAP_IV_E: int +WC_CLEANUP_E: int +ECC_CDH_KAT_FIPS_E: int +DH_CHECK_PUB_E: int +BAD_PATH_ERROR: int +ASYNC_OP_E: int +ECC_PRIVATEONLY_E: int +EXTKEYUSAGE_E: int +WC_HW_E: int +WC_HW_WAIT_E: int +PSS_SALTLEN_E: int +PRIME_GEN_E: int +BER_INDEF_E: int +RSA_OUT_OF_RANGE_E: int +RSAPSS_PAT_FIPS_E: int +ECDSA_PAT_FIPS_E: int +DH_KAT_FIPS_E: int +AESCCM_KAT_FIPS_E: int +SHA3_KAT_FIPS_E: int +ECDHE_KAT_FIPS_E: int +AES_GCM_OVERFLOW_E: int +AES_CCM_OVERFLOW_E: int +RSA_KEY_PAIR_E: int +DH_CHECK_PRIV_E: int +WC_AFALG_SOCK_E: int +WC_DEVCRYPTO_E: int +ZLIB_INIT_ERROR: int +ZLIB_COMPRESS_ERROR: int +ZLIB_DECOMPRESS_ERROR: int +PKCS7_NO_SIGNER_E: int +WC_PKCS7_WANT_READ_E: int +CRYPTOCB_UNAVAILABLE: int +PKCS7_SIGNEEDS_CHECK: int +PSS_SALTLEN_RECOVER_E: int +CHACHA_POLY_OVERFLOW: int +ASN_SELF_SIGNED_E: int +SAKKE_VERIFY_FAIL_E: int +MISSING_IV: int +MISSING_KEY: int +BAD_LENGTH_E: int +ECDSA_KAT_FIPS_E: int +RSA_PAT_FIPS_E: int +KDF_TLS12_KAT_FIPS_E: int +KDF_TLS13_KAT_FIPS_E: int +KDF_SSH_KAT_FIPS_E: int +DHE_PCT_E: int +ECC_PCT_E: int +FIPS_PRIVATE_KEY_LOCKED_E: int +PROTOCOLCB_UNAVAILABLE: int +AES_SIV_AUTH_E: int +NO_VALID_DEVID: int +IO_FAILED_E: int +SYSLIB_FAILED_E: int +USE_HW_PSK: int +ENTROPY_RT_E: int +ENTROPY_APT_E: int +ASN_DEPTH_E: int +ASN_LEN_E: int +SM4_GCM_AUTH_E: int +SM4_CCM_AUTH_E: int +WC_SPAN1_LAST_E: int +WC_SPAN1_MIN_CODE_E: int +WC_SPAN2_FIRST_E: int +DEADLOCK_AVERTED_E: int +ASCON_AUTH_E: int +WC_ACCEL_INHIBIT_E: int +BAD_INDEX_E: int +INTERRUPTED_E: int +WC_SPAN2_LAST_E: int +WC_LAST_E: int +WC_SPAN2_MIN_CODE_E: int +MIN_CODE_E: int +# end of the error codes + FIPS_VERSION: int WC_MGF1NONE: int diff --git a/wolfcrypt/asn.py b/wolfcrypt/asn.py index a726b78..988716f 100644 --- a/wolfcrypt/asn.py +++ b/wolfcrypt/asn.py @@ -28,6 +28,7 @@ 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 +from .types import SupportsRsaSign, SupportsRsaVerify if _lib.SHA_ENABLED: from wolfcrypt.hashes import Sha # ty: ignore[possibly-missing-import] @@ -78,7 +79,7 @@ if _lib.ASN_ENABLED: else: raise WolfCryptError(f"Unknown hash class {hash_cls.__name__}") - def make_signature(data: bytes, hash_cls: type[_Hash], key = None) -> bytes: + def make_signature(data: bytes, hash_cls: type[_Hash], key: SupportsRsaSign | None = None) -> bytes: hash_obj = hash_cls() hash_obj.update(data) digest = hash_obj.digest() @@ -96,7 +97,7 @@ if _lib.ASN_ENABLED: else: return plaintext_sig - def check_signature(signature: bytes, data: bytes, hash_cls: type[_Hash], pub_key) -> bool: + def check_signature(signature: bytes, data: bytes, hash_cls: type[_Hash], pub_key: SupportsRsaVerify) -> bool: computed_signature = make_signature(data, hash_cls) decrypted_signature = pub_key.verify(signature) return _hmac.compare_digest(computed_signature, decrypted_signature) diff --git a/wolfcrypt/ciphers.py b/wolfcrypt/ciphers.py index 2b71d13..bf8a571 100644 --- a/wolfcrypt/ciphers.py +++ b/wolfcrypt/ciphers.py @@ -23,6 +23,7 @@ from __future__ import annotations from abc import ABC, abstractmethod +from collections.abc import Sequence from enum import IntEnum from typing_extensions import override @@ -32,6 +33,7 @@ from wolfcrypt.exceptions import WolfCryptError, WolfCryptApiError from wolfcrypt.hashes import hash_type_to_cls from wolfcrypt.random import Random from wolfcrypt.utils import BytesOrStr, t2b +from .types import SupportsRsaSign, SupportsRsaVerify if _lib.ASN_ENABLED: from wolfcrypt.asn import pem_to_der # ty: ignore[possibly-missing-import] @@ -185,7 +187,7 @@ class _Cipher(ABC): def _decrypt(self, destination: _ffi.CData, source: bytes) -> int: ... @classmethod - def new(cls, key: BytesOrStr, mode: int, IV: BytesOrStr | None = None) -> _Cipher: # pylint: disable=W0613 + def new(cls, key: BytesOrStr, mode: int, IV: BytesOrStr | None = None, **kwargs: int) -> _Cipher: # pylint: disable=W0613 """ Returns a ciphering object, using the secret key contained in the string **key**, and using the feedback mode **mode**, which @@ -326,7 +328,7 @@ if _lib.AES_SIV_ENABLED: if len(self._key) not in AesSiv._key_sizes: raise ValueError(f"key must be {AesSiv._key_sizes} in length, not {len(self._key)}") - def encrypt(self, associated_data: BytesOrStr | list[BytesOrStr], nonce: BytesOrStr, plaintext: BytesOrStr) -> tuple[bytes, bytes]: + def encrypt(self, associated_data: BytesOrStr | Sequence[bytes] | Sequence[bytearray] | Sequence[str] | Sequence[memoryview], nonce: BytesOrStr, plaintext: BytesOrStr) -> tuple[bytes, bytes]: """ Encrypt plaintext data using the nonce provided. The associated data is not encrypted but is included in the authentication tag. @@ -353,7 +355,7 @@ if _lib.AES_SIV_ENABLED: raise WolfCryptApiError("AES-SIV encryption error", ret) return _ffi.buffer(siv)[:], _ffi.buffer(ciphertext)[:] - def decrypt(self, associated_data: BytesOrStr | list[BytesOrStr], nonce: BytesOrStr, siv: BytesOrStr, ciphertext: BytesOrStr) -> bytes: + def decrypt(self, associated_data: BytesOrStr | Sequence[bytes] | Sequence[bytearray] | Sequence[str] | Sequence[memoryview], nonce: BytesOrStr, siv: BytesOrStr, ciphertext: BytesOrStr) -> bytes: """ Decrypt the ciphertext using the nonce and SIV provided. The integrity of the associated data is checked. @@ -383,7 +385,7 @@ if _lib.AES_SIV_ENABLED: return _ffi.buffer(plaintext)[:] @staticmethod - def _prepare_associated_data(associated_data: BytesOrStr | list[BytesOrStr]) -> tuple[_ffi.CData, bytes | list[bytes]]: + def _prepare_associated_data(associated_data: BytesOrStr | Sequence[bytes] | Sequence[bytearray] | Sequence[str] | Sequence[memoryview]) -> tuple[_ffi.CData, bytes | list[bytes]]: """ Prepare associated data for sending to C library. @@ -801,7 +803,7 @@ if _lib.RSA_ENABLED: - class RsaPublic(_Rsa): + class RsaPublic(_Rsa, SupportsRsaVerify): def __init__(self, key: BytesOrStr, hash_type: int | None = None, rng: Random | None = None) -> None: super().__init__(rng) @@ -870,6 +872,7 @@ if _lib.RSA_ENABLED: return _ffi.buffer(ciphertext)[:] + @override def verify(self, signature: BytesOrStr) -> bytes: """ Verifies **signature**, using the public key data in the @@ -933,7 +936,7 @@ if _lib.RSA_ENABLED: return ret == 0 - class RsaPrivate(RsaPublic): + class RsaPrivate(RsaPublic, SupportsRsaSign): if _lib.KEYGEN_ENABLED: @classmethod def make_key(cls, size: int, rng: Random | None = None, hash_type: int | None = None) -> RsaPrivate: @@ -1067,6 +1070,7 @@ if _lib.RSA_ENABLED: return _ffi.buffer(plaintext, ret)[:] + @override def sign(self, plaintext: BytesOrStr) -> bytes: """ Signs **plaintext**, using the private key data in the object. @@ -1424,7 +1428,7 @@ if _lib.ECC_ENABLED: return _ffi.buffer(Qx, qx_size[0])[:], _ffi.buffer(Qy, qy_size[0])[:], _ffi.buffer(d, d_size[0])[:] - def shared_secret(self, peer: EccPrivate) -> bytes: + def shared_secret(self, peer: EccPublic) -> bytes: """ Generates a new secret key using the private key data in the object and the peer's public key. @@ -2562,6 +2566,9 @@ if _lib.ML_DSA_ENABLED: out_size = _ffi.new("word32 *") out_size[0] = in_size + if not isinstance(seed, (list, tuple, bytes)): + raise TypeError("seed must be bytes or list/tuple") + if len(seed) != ML_DSA_SIGNATURE_SEED_LENGTH: raise ValueError( f"Seed for generating a signature must be {ML_DSA_SIGNATURE_SEED_LENGTH} bytes." diff --git a/wolfcrypt/hkdf.py b/wolfcrypt/hkdf.py index a4ddcdb..d11b619 100644 --- a/wolfcrypt/hkdf.py +++ b/wolfcrypt/hkdf.py @@ -30,7 +30,7 @@ from wolfcrypt.utils import t2b if _lib.HKDF_ENABLED: from wolfcrypt.hashes import _Hmac # ty: ignore[possibly-missing-import] - def HKDF(hash_cls: _Hmac, in_key: bytes | str, salt: bytes | str | None = None, info: bytes | str | None = None, out_len: int | None = None) -> bytes: + def HKDF(hash_cls: type[_Hmac], in_key: bytes | str, salt: bytes | str | None = None, info: bytes | str | None = None, out_len: int | None = None) -> bytes: """ Perform HKDF Extract-and-Expand in one call (wraps wc_HKDF). @@ -75,7 +75,7 @@ if _lib.HKDF_ENABLED: return _ffi.buffer(out, out_len)[:] - def HKDF_Extract(hash_cls: _Hmac, salt: bytes | str | None, in_key: bytes | str) -> bytes: + def HKDF_Extract(hash_cls: type[_Hmac], salt: bytes | str | None, in_key: bytes | str) -> bytes: """ HKDF-Extract: PRK = HMAC-Hash(salt, IKM) Wraps wc_HKDF_Extract. @@ -102,7 +102,7 @@ if _lib.HKDF_ENABLED: return _ffi.buffer(out, out_len)[:] - def HKDF_Expand(hash_cls: _Hmac, prk: bytes | str, info: bytes | str | None, out_len: int) -> bytes: + def HKDF_Expand(hash_cls: type[_Hmac], prk: bytes | str, info: bytes | str | None, out_len: int) -> bytes: """ HKDF-Expand: OKM = HKDF-Expand(PRK, info, L) Wraps wc_HKDF_Expand. diff --git a/wolfcrypt/types.py b/wolfcrypt/types.py new file mode 100644 index 0000000..116147c --- /dev/null +++ b/wolfcrypt/types.py @@ -0,0 +1,35 @@ +# types.py +# +# Copyright (C) 2026 wolfSSL Inc. +# +# This file is part of wolfSSL. (formerly known as CyaSSL) +# +# wolfSSL is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# wolfSSL is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 abc import abstractmethod +from typing import Protocol + +from .utils import BytesOrStr + + +class SupportsRsaSign(Protocol): + @abstractmethod + def sign(self, plaintext: BytesOrStr) -> bytes: + raise NotImplementedError + +class SupportsRsaVerify(Protocol): + @abstractmethod + def verify(self, signature: BytesOrStr) -> bytes: + raise NotImplementedError