diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 3e0c0f3..f9227ea 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -15,7 +15,12 @@ permissions: jobs: build: - runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + os: [ ubuntu-latest, ubuntu-24.04-arm ] + + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v7 diff --git a/scripts/build_ffi.py b/scripts/build_ffi.py index 6807edf..ad747f4 100644 --- a/scripts/build_ffi.py +++ b/scripts/build_ffi.py @@ -23,7 +23,11 @@ import re import subprocess from contextlib import contextmanager -from distutils.util import get_platform +try: + from distutils.util import get_platform +except ImportError: + # Python >= 3.12 without setuptools' distutils shim (unit tests). + from sysconfig import get_platform from cffi import FFI import shutil from wolfcrypt._version import __wolfssl_version__ as version @@ -184,7 +188,8 @@ def make_flags(prefix, fips): else: flags = [] - if get_platform() in ["linux-x86_64", "linux-i686"]: + # The static lib is linked into the shared extension. + if get_platform().startswith("linux"): flags.append("CFLAGS=-fPIC") # install location @@ -350,35 +355,44 @@ def get_features(local_wolfssl, features): with open(file) as f: defines += f.read().splitlines() - features["MPAPI"] = 1 if '#define WOLFSSL_PUBLIC_MP' in defines else 0 - features["SHA"] = 0 if '#define NO_SHA' in defines else 1 - features["SHA256"] = 0 if '#define NO_SHA256' in defines else 1 - features["SHA384"] = 1 if '#define WOLFSSL_SHA384' in defines else 0 - features["SHA512"] = 1 if '#define WOLFSSL_SHA512' in defines else 0 - features["SHA3"] = 1 if '#define WOLFSSL_SHA3' in defines else 0 - features["DES3"] = 0 if '#define NO_DES3' in defines else 1 - features["AES"] = 0 if '#define NO_AES' in defines else 1 - features["AES_SIV"] = 1 if '#define WOLFSSL_AES_SIV' in defines else 0 - features["CHACHA"] = 1 if '#define HAVE_CHACHA' in defines else 0 - features["HMAC"] = 0 if '#define NO_HMAC' in defines else 1 - features["RSA"] = 0 if '#define NO_RSA' in defines else 1 - features["ECC_TIMING_RESISTANCE"] = 1 if '#define ECC_TIMING_RESISTANT' in defines else 0 - features["RSA_BLINDING"] = 1 if '#define WC_RSA_BLINDING' in defines else 0 - features["ECC"] = 1 if '#define HAVE_ECC' in defines else 0 - features["ED25519"] = 1 if '#define HAVE_ED25519' in defines else 0 - features["ED448"] = 1 if '#define HAVE_ED448' in defines else 0 - features["KEYGEN"] = 1 if '#define WOLFSSL_KEY_GEN' in defines else 0 - features["PWDBASED"] = 0 if '#define NO_PWDBASED' in defines else 1 - features["ERROR_STRINGS"] = 0 if '#define NO_ERROR_STRINGS' in defines else 1 - features["ASN"] = 0 if '#define NO_ASN' in defines else 1 - features["WC_RNG_SEED_CB"] = 1 if '#define WC_RNG_SEED_CB' in defines else 0 - features["AESGCM_STREAM"] = 1 if '#define WOLFSSL_AESGCM_STREAM' in defines else 0 + return detect_features(defines, features, fips) + +def detect_features(defines, features, fips=False): + """Set features from the lines of options.h/user_settings.h.""" + text = "\n".join(defines) + + def defined(name): + return re.search(rf"^\s*#\s*define\s+{name}\b", text, re.MULTILINE) is not None + + features["MPAPI"] = 1 if defined("WOLFSSL_PUBLIC_MP") else 0 + features["SHA"] = 0 if defined("NO_SHA") else 1 + features["SHA256"] = 0 if defined("NO_SHA256") else 1 + features["SHA384"] = 1 if defined("WOLFSSL_SHA384") else 0 + features["SHA512"] = 1 if defined("WOLFSSL_SHA512") else 0 + features["SHA3"] = 1 if defined("WOLFSSL_SHA3") else 0 + features["DES3"] = 0 if defined("NO_DES3") else 1 + features["AES"] = 0 if defined("NO_AES") else 1 + features["AES_SIV"] = 1 if defined("WOLFSSL_AES_SIV") else 0 + features["CHACHA"] = 1 if defined("HAVE_CHACHA") else 0 + features["HMAC"] = 0 if defined("NO_HMAC") else 1 + features["RSA"] = 0 if defined("NO_RSA") else 1 + features["ECC_TIMING_RESISTANCE"] = 1 if defined("ECC_TIMING_RESISTANT") else 0 + features["RSA_BLINDING"] = 1 if defined("WC_RSA_BLINDING") else 0 + features["ECC"] = 1 if defined("HAVE_ECC") else 0 + features["ED25519"] = 1 if defined("HAVE_ED25519") else 0 + features["ED448"] = 1 if defined("HAVE_ED448") else 0 + features["KEYGEN"] = 1 if defined("WOLFSSL_KEY_GEN") else 0 + features["PWDBASED"] = 0 if defined("NO_PWDBASED") else 1 + features["ERROR_STRINGS"] = 0 if defined("NO_ERROR_STRINGS") else 1 + features["ASN"] = 0 if defined("NO_ASN") else 1 + features["WC_RNG_SEED_CB"] = 1 if defined("WC_RNG_SEED_CB") else 0 + features["AESGCM_STREAM"] = 1 if defined("WOLFSSL_AESGCM_STREAM") else 0 # Try to read minimum AESGCM authentication tag size from settings, else use default. min_auth_tag_sz = re.search(r'#define\s+WOLFSSL_MIN_AUTH_TAG_SZ\s+(\d+)', '\n'.join(defines)) features["MIN_AUTH_TAG_SZ"] = int(min_auth_tag_sz.group(1)) if min_auth_tag_sz else 12 - features["RSA_PSS"] = 1 if '#define WC_RSA_PSS' in defines else 0 - features["CHACHA20_POLY1305"] = 1 if ('#define HAVE_CHACHA' in defines and '#define HAVE_POLY1305' in defines) else 0 - features["ML_DSA"] = 1 if ('#define HAVE_DILITHIUM' in defines or '#define WOLFSSL_HAVE_MLDSA' in defines) else 0 + features["RSA_PSS"] = 1 if defined("WC_RSA_PSS") else 0 + features["CHACHA20_POLY1305"] = 1 if (defined("HAVE_CHACHA") and defined("HAVE_POLY1305")) else 0 + features["ML_DSA"] = 1 if (defined("HAVE_DILITHIUM") or defined("WOLFSSL_HAVE_MLDSA")) else 0 # Determine if support for ML-DSA signing & verification without context has been enabled. mldsa_no_context_defines = [ "WOLFSSL_MLDSA_NO_CTX", @@ -387,15 +401,102 @@ def get_features(local_wolfssl, features): "WOLFSSL_DILITHIUM_NO_CTX", "WOLFSSL_DILITHIUM_FIPS204_DRAFT", ] - have_mldsa_no_context_support = re.search(r'#define\s+(' + '|'.join(mldsa_no_context_defines) + r')\s+', '\n'.join(defines)) - features["ML_DSA_NO_CTX"] = 1 if have_mldsa_no_context_support else 0 - features["ML_KEM"] = 1 if '#define WOLFSSL_HAVE_MLKEM' in defines else 0 - features["HKDF"] = 1 if "#define HAVE_HKDF" in defines else 0 + features["ML_DSA_NO_CTX"] = 1 if any(defined(name) for name in mldsa_no_context_defines) else 0 + features["ML_KEM"] = 1 if defined("WOLFSSL_HAVE_MLKEM") else 0 + # hmac.h and hmac.c provide HKDF only without NO_HMAC. + features["HKDF"] = 1 if defined("HAVE_HKDF") and features["HMAC"] else 0 # Unlike the other fatures, HASHDRBG is enabled by default in random.h, unless WC_NO_HASHDRBG or # CUSTOM_RAND_GENERATE_BLOCK is defined. - features["HASHDRBG"] = 0 if ("#define WC_NO_HASHDRBG" in defines or "#define CUSTOM_RAND_GENERATE_BLOCK" in defines) else 1 - - if '#define HAVE_FIPS' in defines: + features["HASHDRBG"] = 0 if (defined("WC_NO_HASHDRBG") or defined("CUSTOM_RAND_GENERATE_BLOCK")) else 1 + # random.h replaces the RNG API with macros under WC_NO_RNG. random.c and + # rsa.c then build no DRBG, seed callback or RSA blinding functions. + features["RNG"] = 0 if defined("WC_NO_RNG") else 1 + if not features["RNG"]: + features["HASHDRBG"] = 0 + features["WC_RNG_SEED_CB"] = 0 + features["RSA_BLINDING"] = 0 + # aes.h declares wc_AesCtrEncrypt only with WOLFSSL_AES_COUNTER. + features["AES_CTR"] = 1 if features["AES"] and defined("WOLFSSL_AES_COUNTER") else 0 + # settings.h derives HAVE_AES_CBC and HAVE_AES_DECRYPT unless NO_AES_CBC/NO_AES_DECRYPT. + features["AES_CBC"] = 1 if features["AES"] and not defined("NO_AES_CBC") else 0 + features["AES_DECRYPT"] = 1 if features["AES"] and not defined("NO_AES_DECRYPT") else 0 + # aes.c builds streaming GCM decryption only with HAVE_AES_DECRYPT or HAVE_AESGCM_DECRYPT. + features["AESGCM_STREAM_DECRYPT"] = 1 if features["AES"] and features["AESGCM_STREAM"] and ( + features["AES_DECRYPT"] or defined("HAVE_AESGCM_DECRYPT")) else 0 + # sha3.c builds each SHA-3 size unless WOLFSSL_NOSHA3_. settings.h + # leaves only SHA3-384 on Xilinx. + xilinx_sha3 = defined("WOLFSSL_XILINX_CRYPT") or defined("WOLFSSL_AFALG_XILINX") + for bits in (224, 256, 384, 512): + disabled = defined(f"WOLFSSL_NOSHA3_{bits}") or (xilinx_sha3 and bits != 384) + features[f"SHA3_{bits}"] = 1 if features["SHA3"] and not disabled else 0 + # pwdbased.c builds wc_PBKDF2 only with HAVE_PBKDF2 and without NO_HMAC. + # settings.h defines HAVE_PBKDF2 unless NO_PBKDF2, and always for PKCS7 and scrypt. + features["PBKDF2"] = 1 if features["PWDBASED"] and features["HMAC"] and ( + not defined("NO_PBKDF2") or defined("HAVE_PBKDF2") or defined("HAVE_PKCS7") + or defined("HAVE_SCRYPT")) else 0 + # asn.c builds the PEM/DER conversions only with certificate support. + # settings.h derives WOLFSSL_PEM_TO_DER and WOLFSSL_DER_TO_PEM. + certs = features["ASN"] and not defined("NO_CERTS") + features["PEM_TO_DER"] = 1 if certs and (defined("WOLFSSL_PEM_TO_DER") or not ( + defined("WOLFSSL_NO_PEM") or defined("NO_CODING"))) else 0 + features["DER_TO_PEM"] = 1 if certs and (defined("WOLFSSL_DER_TO_PEM") + or defined("WOLFSSL_CERT_GEN") or defined("OPENSSL_EXTRA") or defined("OPENSSL_ALL") + or defined("WOLFSSL_DUAL_ALG_CERTS") + or (defined("WOLFSSL_KEY_GEN") and not defined("WOLFSSL_NO_DER_TO_PEM"))) else 0 + # asn.c builds wc_GetPkcs8TraditionalOffset only with HAVE_PKCS8. + # settings.h defines HAVE_PKCS8 unless both NO_PKCS8 and NO_PKCS12. + features["PKCS8"] = 1 if features["ASN"] and (not defined("NO_PKCS8") or not defined("NO_PKCS12") + or defined("HAVE_PKCS8") or defined("HAVE_PKCS12")) else 0 + # rsa.c leaves out RSA operations for the subset macros set by + # --enable-rsapub, --enable-rsavfy and --disable-oaep. + rsa_public_only = defined("WOLFSSL_RSA_PUBLIC_ONLY") + rsa_verify_only = defined("WOLFSSL_RSA_VERIFY_ONLY") + features["RSA_ENCRYPT"] = 1 if features["RSA"] and not rsa_verify_only else 0 + features["RSA_PRIVATE"] = 1 if features["RSA"] and not rsa_public_only else 0 + features["RSA_SIGN"] = 1 if features["RSA_PRIVATE"] and not rsa_verify_only else 0 + features["RSA_VERIFY"] = 1 if features["RSA"] and not defined("WOLFSSL_RSA_VERIFY_INLINE") else 0 + features["RSA_OAEP"] = 1 if features["RSA"] and not defined("WC_NO_RSA_OAEP") else 0 + # settings.h derives the ECC operations unless NO_ECC_. Timing + # resistant signing and DHE need the RNG. Key export needs SP or big + # integer math. + ecc = features["ECC"] + features["ECC_SIGN"] = 1 if ecc and not defined("NO_ECC_SIGN") and ( + features["RNG"] or not defined("ECC_TIMING_RESISTANT")) else 0 + features["ECC_VERIFY"] = 1 if ecc and not defined("NO_ECC_VERIFY") else 0 + features["ECC_DHE"] = 1 if ecc and not defined("NO_ECC_DHE") and features["RNG"] else 0 + features["ECC_KEY_IMPORT"] = 1 if ecc and not defined("NO_ECC_KEY_IMPORT") else 0 + features["ECC_KEY_EXPORT"] = 1 if ecc and not defined("NO_ECC_KEY_EXPORT") and ( + defined("WOLFSSL_SP_MATH") or not defined("NO_BIG_INT")) else 0 + # settings.h derives the Ed25519 operations unless NO_ED25519_. + for op in ("MAKE_KEY", "SIGN", "VERIFY", "KEY_IMPORT", "KEY_EXPORT"): + features[f"ED25519_{op}"] = 1 if features["ED25519"] and not defined(f"NO_ED25519_{op}") else 0 + # settings.h derives the Ed448 operations unless NO_ED448_. + for op in ("SIGN", "VERIFY", "KEY_IMPORT", "KEY_EXPORT"): + features[f"ED448_{op}"] = 1 if features["ED448"] and not defined(f"NO_ED448_{op}") else 0 + # wc_mlkem.c builds each ML-KEM operation unless WOLFSSL_MLKEM_NO_. + # wc_mlkem.h maps the legacy WOLFSSL_KYBER_NO_ names to these. + for op in ("MAKE_KEY", "ENCAPSULATE", "DECAPSULATE"): + features[f"ML_KEM_{op}"] = 1 if features["ML_KEM"] and not ( + defined(f"WOLFSSL_MLKEM_NO_{op}") or defined(f"WOLFSSL_KYBER_NO_{op}")) else 0 + # dilithium.h derives the ML-DSA operations and key parts from + # WOLFSSL_MLDSA_ or the legacy WOLFSSL_DILITHIUM_ names. + def mldsa_defined(gate): + return defined(f"WOLFSSL_MLDSA_{gate}") or defined(f"WOLFSSL_DILITHIUM_{gate}") + + mldsa_verify_only = mldsa_defined("VERIFY_ONLY") + mldsa_no_make_key = mldsa_verify_only or mldsa_defined("NO_MAKE_KEY") + mldsa_no_sign = mldsa_verify_only or mldsa_defined("NO_SIGN") + mldsa_no_verify = mldsa_defined("NO_VERIFY") + ml_dsa = features["ML_DSA"] + features["ML_DSA_MAKE_KEY"] = 1 if ml_dsa and not mldsa_no_make_key else 0 + features["ML_DSA_SIGN"] = 1 if ml_dsa and not mldsa_no_sign else 0 + features["ML_DSA_VERIFY"] = 1 if ml_dsa and not mldsa_no_verify else 0 + features["ML_DSA_PUBLIC_KEY"] = 1 if ml_dsa and (not mldsa_no_make_key or not mldsa_no_verify + or mldsa_defined("PUBLIC_KEY")) else 0 + features["ML_DSA_PRIVATE_KEY"] = 1 if ml_dsa and (not mldsa_no_make_key or not mldsa_no_sign + or mldsa_defined("PRIVATE_KEY")) else 0 + + if defined("HAVE_FIPS"): if not fips: e = "fips.c empty but HAVE_FIPS defined." raise RuntimeError(e) @@ -430,6 +531,15 @@ def build_ffi(local_wolfssl, features): else: cffi_libraries.append("wolfssl") + ffibuilder.set_source( "wolfcrypt._ffi", make_source(features), + include_dirs=cffi_include_dirs, + library_dirs=[wolfssl_lib_dir(local_wolfssl, features["FIPS"])], + libraries=cffi_libraries) + + ffibuilder.cdef(make_cdef(features)) + +def make_source(features): + """Return the C source passed to set_source().""" includes_string = "" if sys.platform == 'win32': @@ -515,13 +625,52 @@ def build_ffi(local_wolfssl, features): int ML_DSA_NO_CTX_ENABLED = {features["ML_DSA_NO_CTX"]}; int HKDF_ENABLED = {features["HKDF"]}; int HASHDRBG_ENABLED = {features["HASHDRBG"]}; + int AES_CTR_ENABLED = {features["AES_CTR"]}; + int AES_CBC_ENABLED = {features["AES_CBC"]}; + int AES_DECRYPT_ENABLED = {features["AES_DECRYPT"]}; + int AESGCM_STREAM_DECRYPT_ENABLED = {features["AESGCM_STREAM_DECRYPT"]}; + int SHA3_224_ENABLED = {features["SHA3_224"]}; + int SHA3_256_ENABLED = {features["SHA3_256"]}; + int SHA3_384_ENABLED = {features["SHA3_384"]}; + int SHA3_512_ENABLED = {features["SHA3_512"]}; + int PBKDF2_ENABLED = {features["PBKDF2"]}; + int PEM_TO_DER_ENABLED = {features["PEM_TO_DER"]}; + int DER_TO_PEM_ENABLED = {features["DER_TO_PEM"]}; + int PKCS8_ENABLED = {features["PKCS8"]}; + int RNG_ENABLED = {features["RNG"]}; + int RSA_ENCRYPT_ENABLED = {features["RSA_ENCRYPT"]}; + int RSA_PRIVATE_ENABLED = {features["RSA_PRIVATE"]}; + int RSA_SIGN_ENABLED = {features["RSA_SIGN"]}; + int RSA_VERIFY_ENABLED = {features["RSA_VERIFY"]}; + int RSA_OAEP_ENABLED = {features["RSA_OAEP"]}; + int ECC_SIGN_ENABLED = {features["ECC_SIGN"]}; + int ECC_VERIFY_ENABLED = {features["ECC_VERIFY"]}; + int ECC_DHE_ENABLED = {features["ECC_DHE"]}; + int ECC_KEY_IMPORT_ENABLED = {features["ECC_KEY_IMPORT"]}; + int ECC_KEY_EXPORT_ENABLED = {features["ECC_KEY_EXPORT"]}; + int ED25519_MAKE_KEY_ENABLED = {features["ED25519_MAKE_KEY"]}; + int ED25519_SIGN_ENABLED = {features["ED25519_SIGN"]}; + int ED25519_VERIFY_ENABLED = {features["ED25519_VERIFY"]}; + int ED25519_KEY_IMPORT_ENABLED = {features["ED25519_KEY_IMPORT"]}; + int ED25519_KEY_EXPORT_ENABLED = {features["ED25519_KEY_EXPORT"]}; + int ED448_SIGN_ENABLED = {features["ED448_SIGN"]}; + int ED448_VERIFY_ENABLED = {features["ED448_VERIFY"]}; + int ED448_KEY_IMPORT_ENABLED = {features["ED448_KEY_IMPORT"]}; + int ED448_KEY_EXPORT_ENABLED = {features["ED448_KEY_EXPORT"]}; + int ML_KEM_MAKE_KEY_ENABLED = {features["ML_KEM_MAKE_KEY"]}; + int ML_KEM_ENCAPSULATE_ENABLED = {features["ML_KEM_ENCAPSULATE"]}; + int ML_KEM_DECAPSULATE_ENABLED = {features["ML_KEM_DECAPSULATE"]}; + int ML_DSA_MAKE_KEY_ENABLED = {features["ML_DSA_MAKE_KEY"]}; + int ML_DSA_SIGN_ENABLED = {features["ML_DSA_SIGN"]}; + int ML_DSA_VERIFY_ENABLED = {features["ML_DSA_VERIFY"]}; + int ML_DSA_PUBLIC_KEY_ENABLED = {features["ML_DSA_PUBLIC_KEY"]}; + int ML_DSA_PRIVATE_KEY_ENABLED = {features["ML_DSA_PRIVATE_KEY"]}; """ - ffibuilder.set_source( "wolfcrypt._ffi", init_source_string, - include_dirs=cffi_include_dirs, - library_dirs=[wolfssl_lib_dir(local_wolfssl, features["FIPS"])], - libraries=cffi_libraries) + return init_source_string +def make_cdef(features): + """Return the cdef for the given features.""" # TODO: change cdef to cdef. # cdef = "" cdef = """ @@ -558,20 +707,62 @@ def build_ffi(local_wolfssl, features): extern int ML_DSA_NO_CTX_ENABLED; extern int HKDF_ENABLED; extern int HASHDRBG_ENABLED; + extern int AES_CTR_ENABLED; + extern int AES_CBC_ENABLED; + extern int AES_DECRYPT_ENABLED; + extern int AESGCM_STREAM_DECRYPT_ENABLED; + extern int SHA3_224_ENABLED; + extern int SHA3_256_ENABLED; + extern int SHA3_384_ENABLED; + extern int SHA3_512_ENABLED; + extern int PBKDF2_ENABLED; + extern int PEM_TO_DER_ENABLED; + extern int DER_TO_PEM_ENABLED; + extern int PKCS8_ENABLED; + extern int RNG_ENABLED; + extern int RSA_ENCRYPT_ENABLED; + extern int RSA_PRIVATE_ENABLED; + extern int RSA_SIGN_ENABLED; + extern int RSA_VERIFY_ENABLED; + extern int RSA_OAEP_ENABLED; + extern int ECC_SIGN_ENABLED; + extern int ECC_VERIFY_ENABLED; + extern int ECC_DHE_ENABLED; + extern int ECC_KEY_IMPORT_ENABLED; + extern int ECC_KEY_EXPORT_ENABLED; + extern int ED25519_MAKE_KEY_ENABLED; + extern int ED25519_SIGN_ENABLED; + extern int ED25519_VERIFY_ENABLED; + extern int ED25519_KEY_IMPORT_ENABLED; + extern int ED25519_KEY_EXPORT_ENABLED; + extern int ED448_SIGN_ENABLED; + extern int ED448_VERIFY_ENABLED; + extern int ED448_KEY_IMPORT_ENABLED; + extern int ED448_KEY_EXPORT_ENABLED; + extern int ML_KEM_MAKE_KEY_ENABLED; + extern int ML_KEM_ENCAPSULATE_ENABLED; + extern int ML_KEM_DECAPSULATE_ENABLED; + extern int ML_DSA_MAKE_KEY_ENABLED; + extern int ML_DSA_SIGN_ENABLED; + extern int ML_DSA_VERIFY_ENABLED; + extern int ML_DSA_PUBLIC_KEY_ENABLED; + extern int ML_DSA_PRIVATE_KEY_ENABLED; typedef unsigned char byte; typedef unsigned int word32; typedef struct { ...; } WC_RNG; typedef struct { ...; } OS_Seed; - + """ + if features["RNG"]: + cdef += """ int wc_InitRng(WC_RNG*); int wc_InitRngNonce(WC_RNG*, byte*, word32); int wc_InitRngNonce_ex(WC_RNG*, byte*, word32, void*, int); int wc_RNG_GenerateBlock(WC_RNG*, byte*, word32); int wc_RNG_GenerateByte(WC_RNG*, byte*); int wc_FreeRng(WC_RNG*); - """ + """ if features["HASHDRBG"]: cdef += """ int wc_RNG_DRBG_Reseed(WC_RNG*, const byte*, word32); @@ -841,7 +1032,7 @@ def build_ffi(local_wolfssl, features): const char* wc_GetErrorString(int error); """ - if not features["FIPS"] or features["FIPS_VERSION"] > 2: + if features["RNG"] and (not features["FIPS"] or features["FIPS_VERSION"] > 2): cdef += """ int wc_GenerateSeed(OS_Seed* os, byte* seed, word32 sz); """ @@ -900,27 +1091,16 @@ def build_ffi(local_wolfssl, features): if features["SHA3"]: cdef += """ typedef struct { ...; } wc_Sha3; - int wc_InitSha3_224(wc_Sha3*, void *, int); - int wc_InitSha3_256(wc_Sha3*, void *, int); - int wc_InitSha3_384(wc_Sha3*, void *, int); - int wc_InitSha3_512(wc_Sha3*, void *, int); - int wc_Sha3_224_Update(wc_Sha3*, const byte*, word32); - int wc_Sha3_256_Update(wc_Sha3*, const byte*, word32); - int wc_Sha3_384_Update(wc_Sha3*, const byte*, word32); - int wc_Sha3_512_Update(wc_Sha3*, const byte*, word32); - int wc_Sha3_224_Final(wc_Sha3*, byte*); - int wc_Sha3_256_Final(wc_Sha3*, byte*); - int wc_Sha3_384_Final(wc_Sha3*, byte*); - int wc_Sha3_512_Final(wc_Sha3*, byte*); - void wc_Sha3_224_Free(wc_Sha3*); - void wc_Sha3_256_Free(wc_Sha3*); - void wc_Sha3_384_Free(wc_Sha3*); - void wc_Sha3_512_Free(wc_Sha3*); - int wc_Sha3_224_Copy(wc_Sha3*, wc_Sha3*); - int wc_Sha3_256_Copy(wc_Sha3*, wc_Sha3*); - int wc_Sha3_384_Copy(wc_Sha3*, wc_Sha3*); - int wc_Sha3_512_Copy(wc_Sha3*, wc_Sha3*); """ + for bits in (224, 256, 384, 512): + if features[f"SHA3_{bits}"]: + cdef += f""" + int wc_InitSha3_{bits}(wc_Sha3*, void *, int); + int wc_Sha3_{bits}_Update(wc_Sha3*, const byte*, word32); + int wc_Sha3_{bits}_Final(wc_Sha3*, byte*); + void wc_Sha3_{bits}_Free(wc_Sha3*); + int wc_Sha3_{bits}_Copy(wc_Sha3*, wc_Sha3*); + """ if features["DES3"]: cdef += """ @@ -935,10 +1115,19 @@ def build_ffi(local_wolfssl, features): typedef struct { ...; } Aes; int wc_AesSetKey(Aes*, const byte*, word32, const byte*, int); - int wc_AesCbcEncrypt(Aes*, byte*, const byte*, word32); - int wc_AesCbcDecrypt(Aes*, byte*, const byte*, word32); - int wc_AesCtrEncrypt(Aes*, byte*, const byte*, word32); """ + if features["AES_CBC"]: + cdef += """ + int wc_AesCbcEncrypt(Aes*, byte*, const byte*, word32); + """ + if features["AES_CBC"] and features["AES_DECRYPT"]: + cdef += """ + int wc_AesCbcDecrypt(Aes*, byte*, const byte*, word32); + """ + if features["AES_CTR"]: + cdef += """ + int wc_AesCtrEncrypt(Aes*, byte*, const byte*, word32); + """ if features["AES"] and features["AESGCM_STREAM"]: cdef += """ @@ -953,14 +1142,17 @@ def build_ffi(local_wolfssl, features): word32 sz, const byte* authIn, word32 authInSz); int wc_AesGcmEncryptFinal(Aes* aes, byte* authTag, word32 authTagSz); - int wc_AesGcmDecryptInit(Aes* aes, const byte* key, word32 len, - const byte* iv, word32 ivSz); - int wc_AesGcmDecryptUpdate(Aes* aes, byte* out, const byte* in, - word32 sz, const byte* authIn, word32 authInSz); - int wc_AesGcmDecryptFinal(Aes* aes, const byte* authTag, - word32 authTagSz); void wc_AesFree(Aes* aes); """ + if features["AESGCM_STREAM_DECRYPT"]: + cdef += """ + int wc_AesGcmDecryptInit(Aes* aes, const byte* key, word32 len, + const byte* iv, word32 ivSz); + int wc_AesGcmDecryptUpdate(Aes* aes, byte* out, const byte* in, + word32 sz, const byte* authIn, word32 authInSz); + int wc_AesGcmDecryptFinal(Aes* aes, const byte* authTag, + word32 authTagSz); + """ if features["AES"] and features["AES_SIV"]: cdef += """ @@ -1019,6 +1211,11 @@ def build_ffi(local_wolfssl, features): void wc_HmacFree(Hmac*); """ + cdef += """ + #define WC_MIN_DIGEST_SIZE ... + #define WC_MAX_DIGEST_SIZE ... + """ + if features["RSA"]: cdef += """ static const int WC_RSA_PKCSV15_PAD; @@ -1057,27 +1254,52 @@ def build_ffi(local_wolfssl, features): int wc_RsaPrivateKeyDecode(const byte*, word32*, RsaKey*, word32); int wc_RsaPublicKeyDecode(const byte*, word32*, RsaKey*, word32); int wc_RsaEncryptSize(RsaKey*); - - int wc_RsaPrivateDecrypt(const byte*, word32, byte*, word32, - RsaKey* key); - int wc_RsaPublicEncrypt(const byte*, word32, byte*, word32, - RsaKey*, WC_RNG*); - int wc_RsaPublicEncrypt_ex(const byte* in, word32 inLen, byte* out, - word32 outLen, RsaKey* key, WC_RNG* rng, int type, - enum wc_HashType hash, int mgf, byte* label, - word32 labelSz); - int wc_RsaPrivateDecrypt_ex(const byte* in, word32 inLen, - byte* out, word32 outLen, RsaKey* key, int type, - enum wc_HashType hash, int mgf, byte* label, - word32 labelSz); - int wc_RsaSSL_Sign(const byte*, word32, byte*, word32, RsaKey*, WC_RNG*); - int wc_RsaSSL_Verify(const byte*, word32, byte*, word32, RsaKey*); """ - if features["RSA_PSS"]: + if features["RSA_ENCRYPT"]: + cdef += """ + int wc_RsaPublicEncrypt(const byte*, word32, byte*, word32, + RsaKey*, WC_RNG*); + """ + if features["RSA_OAEP"]: + cdef += """ + int wc_RsaPublicEncrypt_ex(const byte* in, word32 inLen, byte* out, + word32 outLen, RsaKey* key, WC_RNG* rng, int type, + enum wc_HashType hash, int mgf, byte* label, + word32 labelSz); + """ + + if features["RSA_PRIVATE"]: + cdef += """ + int wc_RsaPrivateDecrypt(const byte*, word32, byte*, word32, + RsaKey* key); + """ + if features["RSA_OAEP"]: + cdef += """ + int wc_RsaPrivateDecrypt_ex(const byte* in, word32 inLen, + byte* out, word32 outLen, RsaKey* key, int type, + enum wc_HashType hash, int mgf, byte* label, + word32 labelSz); + """ + + if features["RSA_SIGN"]: + cdef += """ + int wc_RsaSSL_Sign(const byte*, word32, byte*, word32, RsaKey*, WC_RNG*); + """ + + if features["RSA_VERIFY"]: + cdef += """ + int wc_RsaSSL_Verify(const byte*, word32, byte*, word32, RsaKey*); + """ + + if features["RSA_PSS"] and features["RSA_SIGN"]: cdef += """ int wc_RsaPSS_Sign(const byte* in, word32 inLen, byte* out, word32 outLen, enum wc_HashType hash, int mgf, RsaKey* key, WC_RNG* rng); + """ + + if features["RSA_PSS"]: + cdef += """ int wc_RsaPSS_Verify(const byte* in, word32 inLen, byte* out, word32 outLen, enum wc_HashType hash, int mgf, RsaKey* key); int wc_RsaPSS_CheckPadding(const byte* in, word32 inSz, byte* sig, @@ -1089,9 +1311,13 @@ def build_ffi(local_wolfssl, features): int wc_RsaSetRNG(RsaKey* key, WC_RNG* rng); """ - if features["KEYGEN"]: + if features["KEYGEN"] and features["RSA_PRIVATE"]: cdef += """ int wc_MakeRsaKey(RsaKey* key, int size, long e, WC_RNG* rng); + """ + + if features["KEYGEN"]: + cdef += """ int wc_RsaKeyToDer(RsaKey* key, byte* output, word32 inLen); int wc_RsaKeyToPublicDer(RsaKey* key, byte* output, word32 inLen); @@ -1099,7 +1325,8 @@ def build_ffi(local_wolfssl, features): if features["ECC"]: cdef += """ - typedef struct {...; } ecc_key; + typedef struct { int type; ...; } ecc_key; + static const int ECC_PRIVATEKEY; int wc_ecc_init(ecc_key* ecc); void wc_ecc_free(ecc_key* ecc); @@ -1107,43 +1334,61 @@ def build_ffi(local_wolfssl, features): int wc_ecc_make_key(WC_RNG* rng, int keysize, ecc_key* key); int wc_ecc_size(ecc_key* key); int wc_ecc_sig_size(ecc_key* key); - - int wc_EccPrivateKeyDecode(const byte*, word32*, ecc_key*, word32); - int wc_EccKeyToDer(ecc_key*, byte* output, word32 inLen); - - int wc_EccPublicKeyDecode(const byte*, word32*, ecc_key*, word32); - int wc_EccPublicKeyToDer(ecc_key*, byte* output, - word32 inLen, int with_AlgCurve); - - int wc_ecc_export_x963(ecc_key*, byte* out, word32* outLen); - int wc_ecc_import_x963(const byte* in, word32 inLen, ecc_key* key); - int wc_ecc_export_private_raw(ecc_key* key, byte* qx, word32* qxLen, - byte* qy, word32* qyLen, byte* d, word32* dLen); - int wc_ecc_import_unsigned(ecc_key* key, const byte* qx, const byte* qy, - const byte* d, int curve_id); - int wc_ecc_export_public_raw(ecc_key* key, byte* qx, word32* qxLen, - byte* qy, word32* qyLen); int wc_ecc_get_curve_size_from_id(int curve_id); + int wc_ecc_check_key(ecc_key* key); + """ + if features["ECC_KEY_IMPORT"]: + cdef += """ + int wc_EccPrivateKeyDecode(const byte*, word32*, ecc_key*, word32); + int wc_EccPublicKeyDecode(const byte*, word32*, ecc_key*, word32); + int wc_ecc_import_x963(const byte* in, word32 inLen, ecc_key* key); + int wc_ecc_import_unsigned(ecc_key* key, const byte* qx, const byte* qy, + const byte* d, int curve_id); + """ + + if features["ECC_KEY_EXPORT"]: + cdef += """ + int wc_EccKeyToDer(ecc_key*, byte* output, word32 inLen); + int wc_EccKeyDerSize(ecc_key*, int pub); + int wc_EccPublicKeyToDer(ecc_key*, byte* output, + word32 inLen, int with_AlgCurve); + int wc_ecc_export_x963(ecc_key*, byte* out, word32* outLen); + int wc_ecc_export_private_raw(ecc_key* key, byte* qx, word32* qxLen, + byte* qy, word32* qyLen, byte* d, word32* dLen); + int wc_ecc_export_public_raw(ecc_key* key, byte* qx, word32* qxLen, + byte* qy, word32* qyLen); + """ - int wc_ecc_shared_secret(ecc_key* private_key, ecc_key* public_key, - byte* out, word32* outlen); + if features["ECC_DHE"]: + cdef += """ + int wc_ecc_shared_secret(ecc_key* private_key, ecc_key* public_key, + byte* out, word32* outlen); + """ - int wc_ecc_sign_hash(const byte* in, word32 inlen, - byte* out, word32 *outlen, - WC_RNG* rng, ecc_key* key); - int wc_ecc_verify_hash(const byte* sig, word32 siglen, - const byte* hash, word32 hashlen, - int* stat, ecc_key* key); - """ + if features["ECC_SIGN"]: + cdef += """ + int wc_ecc_sign_hash(const byte* in, word32 inlen, + byte* out, word32 *outlen, + WC_RNG* rng, ecc_key* key); + """ + if features["MPAPI"]: + cdef += """ + int wc_ecc_sign_hash_ex(const byte* in, word32 inlen, WC_RNG* rng, + ecc_key* key, mp_int *r, mp_int *s); + """ - if features["MPAPI"]: + if features["ECC_VERIFY"]: cdef += """ - int wc_ecc_sign_hash_ex(const byte* in, word32 inlen, WC_RNG* rng, - ecc_key* key, mp_int *r, mp_int *s); - int wc_ecc_verify_hash_ex(mp_int *r, mp_int *s, const byte* hash, - word32 hashlen, int* res, ecc_key* key); + int wc_ecc_verify_hash(const byte* sig, word32 siglen, + const byte* hash, word32 hashlen, + int* stat, ecc_key* key); """ + if features["MPAPI"]: + cdef += """ + int wc_ecc_verify_hash_ex(mp_int *r, mp_int *s, const byte* hash, + word32 hashlen, int* res, ecc_key* key); + """ if features["ECC_TIMING_RESISTANCE"] and (not features["FIPS"] or features["FIPS_VERSION"] > 2): @@ -1159,34 +1404,52 @@ def build_ffi(local_wolfssl, features): int wc_ed25519_init(ed25519_key* ed25519); void wc_ed25519_free(ed25519_key* ed25519); - int wc_ed25519_make_key(WC_RNG* rng, int keysize, ed25519_key* key); - int wc_ed25519_make_public(ed25519_key* key, unsigned char* pubKey, - word32 pubKeySz); int wc_ed25519_size(ed25519_key* key); int wc_ed25519_sig_size(ed25519_key* key); - int wc_ed25519_sign_msg(const byte* in, word32 inlen, byte* out, - word32 *outlen, ed25519_key* key); - int wc_ed25519_verify_msg(const byte* sig, word32 siglen, const byte* msg, - word32 msglen, int* stat, ed25519_key* key); - int wc_Ed25519PrivateKeyDecode(const byte*, word32*, ed25519_key*, word32); - int wc_Ed25519KeyToDer(ed25519_key*, byte* output, word32 inLen); - - int wc_Ed25519PublicKeyDecode(const byte*, word32*, ed25519_key*, word32); - int wc_Ed25519PublicKeyToDer(ed25519_key*, byte* output, - word32 inLen, int with_AlgCurve); - - int wc_ed25519_import_public(const byte* in, word32 inLen, ed25519_key* key); - int wc_ed25519_import_private_only(const byte* priv, word32 privSz, ed25519_key* key); - int wc_ed25519_import_private_key(const byte* priv, word32 privSz, const byte* pub, word32 pubSz, ed25519_key* key); - int wc_ed25519_export_public(ed25519_key*, byte* out, word32* outLen); - int wc_ed25519_export_private_only(ed25519_key* key, byte* out, word32* outLen); - int wc_ed25519_export_private(ed25519_key* key, byte* out, word32* outLen); - int wc_ed25519_export_key(ed25519_key* key, byte* priv, word32 *privSz, byte* pub, word32 *pubSz); int wc_ed25519_check_key(ed25519_key* key); int wc_ed25519_pub_size(ed25519_key* key); int wc_ed25519_priv_size(ed25519_key* key); """ + if features["ED25519_MAKE_KEY"]: + cdef += """ + int wc_ed25519_make_key(WC_RNG* rng, int keysize, ed25519_key* key); + int wc_ed25519_make_public(ed25519_key* key, unsigned char* pubKey, + word32 pubKeySz); + """ + + if features["ED25519_SIGN"]: + cdef += """ + int wc_ed25519_sign_msg(const byte* in, word32 inlen, byte* out, + word32 *outlen, ed25519_key* key); + """ + + if features["ED25519_VERIFY"]: + cdef += """ + int wc_ed25519_verify_msg(const byte* sig, word32 siglen, const byte* msg, + word32 msglen, int* stat, ed25519_key* key); + """ + + if features["ED25519_KEY_IMPORT"]: + cdef += """ + int wc_Ed25519PrivateKeyDecode(const byte*, word32*, ed25519_key*, word32); + int wc_Ed25519PublicKeyDecode(const byte*, word32*, ed25519_key*, word32); + int wc_ed25519_import_public(const byte* in, word32 inLen, ed25519_key* key); + int wc_ed25519_import_private_only(const byte* priv, word32 privSz, ed25519_key* key); + int wc_ed25519_import_private_key(const byte* priv, word32 privSz, const byte* pub, word32 pubSz, ed25519_key* key); + """ + + if features["ED25519_KEY_EXPORT"]: + cdef += """ + int wc_Ed25519KeyToDer(ed25519_key*, byte* output, word32 inLen); + int wc_Ed25519PublicKeyToDer(ed25519_key*, byte* output, + word32 inLen, int with_AlgCurve); + int wc_ed25519_export_public(ed25519_key*, byte* out, word32* outLen); + int wc_ed25519_export_private_only(ed25519_key* key, byte* out, word32* outLen); + int wc_ed25519_export_private(ed25519_key* key, byte* out, word32* outLen); + int wc_ed25519_export_key(ed25519_key* key, byte* priv, word32 *privSz, byte* pub, word32 *pubSz); + """ + if features["ED448"]: cdef += """ typedef struct {...; } ed448_key; @@ -1199,31 +1462,45 @@ def build_ffi(local_wolfssl, features): word32 pubKeySz); int wc_ed448_size(ed448_key* key); int wc_ed448_sig_size(ed448_key* key); - int wc_ed448_sign_msg(const byte* in, word32 inlen, byte* out, - word32 *outlen, ed448_key* key, const byte* ctx, - byte ctx_len); - int wc_ed448_verify_msg(const byte* sig, word32 siglen, const byte* msg, - word32 msglen, int* stat, ed448_key* key, const byte *ctx, - byte ctx_len); - int wc_Ed448PrivateKeyDecode(const byte*, word32*, ed448_key*, word32); - int wc_Ed448KeyToDer(ed448_key*, byte* output, word32 inLen); - - int wc_Ed448PublicKeyDecode(const byte*, word32*, ed448_key*, word32); - int wc_Ed448PublicKeyToDer(ed448_key*, byte* output, - word32 inLen, int with_AlgCurve); - - int wc_ed448_import_public(const byte* in, word32 inLen, ed448_key* key); - int wc_ed448_import_private_only(const byte* priv, word32 privSz, ed448_key* key); - int wc_ed448_import_private_key(const byte* priv, word32 privSz, const byte* pub, word32 pubSz, ed448_key* key); - int wc_ed448_export_public(ed448_key*, byte* out, word32* outLen); - int wc_ed448_export_private_only(ed448_key* key, byte* out, word32* outLen); - int wc_ed448_export_private(ed448_key* key, byte* out, word32* outLen); - int wc_ed448_export_key(ed448_key* key, byte* priv, word32 *privSz, byte* pub, word32 *pubSz); int wc_ed448_check_key(ed448_key* key); int wc_ed448_pub_size(ed448_key* key); int wc_ed448_priv_size(ed448_key* key); """ + if features["ED448_SIGN"]: + cdef += """ + int wc_ed448_sign_msg(const byte* in, word32 inlen, byte* out, + word32 *outlen, ed448_key* key, const byte* ctx, + byte ctx_len); + """ + + if features["ED448_VERIFY"]: + cdef += """ + int wc_ed448_verify_msg(const byte* sig, word32 siglen, const byte* msg, + word32 msglen, int* stat, ed448_key* key, const byte *ctx, + byte ctx_len); + """ + + if features["ED448_KEY_IMPORT"]: + cdef += """ + int wc_Ed448PrivateKeyDecode(const byte*, word32*, ed448_key*, word32); + int wc_Ed448PublicKeyDecode(const byte*, word32*, ed448_key*, word32); + int wc_ed448_import_public(const byte* in, word32 inLen, ed448_key* key); + int wc_ed448_import_private_only(const byte* priv, word32 privSz, ed448_key* key); + int wc_ed448_import_private_key(const byte* priv, word32 privSz, const byte* pub, word32 pubSz, ed448_key* key); + """ + + if features["ED448_KEY_EXPORT"]: + cdef += """ + int wc_Ed448KeyToDer(ed448_key*, byte* output, word32 inLen); + int wc_Ed448PublicKeyToDer(ed448_key*, byte* output, + word32 inLen, int with_AlgCurve); + int wc_ed448_export_public(ed448_key*, byte* out, word32* outLen); + int wc_ed448_export_private_only(ed448_key* key, byte* out, word32* outLen); + int wc_ed448_export_private(ed448_key* key, byte* out, word32* outLen); + int wc_ed448_export_key(ed448_key* key, byte* priv, word32 *privSz, byte* pub, word32 *pubSz); + """ + if features["HKDF"]: cdef += """ int wc_HKDF(int type, const byte* inKey, word32 inKeySz, @@ -1244,7 +1521,7 @@ def build_ffi(local_wolfssl, features): void* heap, int devId); """ - if features["PWDBASED"]: + if features["PBKDF2"]: cdef += """ int wc_PBKDF2(byte* output, const byte* passwd, int pLen, const byte* salt, int sLen, int iterations, int kLen, @@ -1262,6 +1539,12 @@ def build_ffi(local_wolfssl, features): static const long SHA384h; static const long SHA512h; + word32 wc_EncodeSignature(byte* out, const byte* digest, word32 digSz, + int hashOID); + """ + + if features["PEM_TO_DER"]: + cdef += """ typedef struct DerBuffer { byte* buffer; void* heap; @@ -1271,18 +1554,19 @@ def build_ffi(local_wolfssl, features): } DerBuffer; typedef struct { ...; } EncryptedInfo; - word32 wc_EncodeSignature(byte* out, const byte* digest, word32 digSz, - int hashOID); int wc_PemToDer(const unsigned char* buff, long longSz, int type, DerBuffer** pDer, void* heap, EncryptedInfo* info, int* keyFormat); void wc_FreeDer(DerBuffer** pDer); + """ + + if features["DER_TO_PEM"]: + cdef += """ int wc_DerToPemEx(const byte* der, word32 derSz, byte* output, word32 outSz, byte *cipher_info, int type); """ - if features["ASN"] or features["RSA"]: - # This ASN function is used by the RSA binding as well. + if features["PKCS8"]: cdef += """ int wc_GetPkcs8TraditionalOffset(byte* input, word32* inOutIdx, word32 sz); """ @@ -1321,17 +1605,29 @@ def build_ffi(local_wolfssl, features): int wc_KyberKey_PublicKeySize(KyberKey* key, word32* len); int wc_KyberKey_Init(int type, KyberKey* key, void* heap, int devId); void wc_KyberKey_Free(KyberKey* key); - int wc_KyberKey_MakeKey(KyberKey* key, WC_RNG* rng); - int wc_KyberKey_MakeKeyWithRandom(KyberKey* key, const unsigned char* rand, int len); int wc_KyberKey_EncodePublicKey(KyberKey* key, unsigned char* out, word32 len); int wc_KyberKey_DecodePublicKey(KyberKey* key, const unsigned char* in, word32 len); - int wc_KyberKey_Encapsulate(KyberKey* key, unsigned char* ct, unsigned char* ss, WC_RNG* rng); - int wc_KyberKey_EncapsulateWithRandom(KyberKey* key, unsigned char* ct, unsigned char* ss, const unsigned char* rand, int len); - int wc_KyberKey_Decapsulate(KyberKey* key, unsigned char* ss, const unsigned char* ct, word32 len); int wc_KyberKey_EncodePrivateKey(KyberKey* key, unsigned char* out, word32 len); int wc_KyberKey_DecodePrivateKey(KyberKey* key, const unsigned char* in, word32 len); """ + if features["ML_KEM_MAKE_KEY"]: + cdef += """ + int wc_KyberKey_MakeKey(KyberKey* key, WC_RNG* rng); + int wc_KyberKey_MakeKeyWithRandom(KyberKey* key, const unsigned char* rand, int len); + """ + + if features["ML_KEM_ENCAPSULATE"]: + cdef += """ + int wc_KyberKey_Encapsulate(KyberKey* key, unsigned char* ct, unsigned char* ss, WC_RNG* rng); + int wc_KyberKey_EncapsulateWithRandom(KyberKey* key, unsigned char* ct, unsigned char* ss, const unsigned char* rand, int len); + """ + + if features["ML_KEM_DECAPSULATE"]: + cdef += """ + int wc_KyberKey_Decapsulate(KyberKey* key, unsigned char* ss, const unsigned char* ct, word32 len); + """ + if features["ML_DSA"]: cdef += """ static const int DILITHIUM_SEED_SZ; @@ -1342,31 +1638,62 @@ def build_ffi(local_wolfssl, features): int wc_dilithium_init_ex(dilithium_key* key, void* heap, int devId); int wc_dilithium_set_level(dilithium_key* key, byte level); void wc_dilithium_free(dilithium_key* key); - int wc_dilithium_make_key(dilithium_key* key, WC_RNG* rng); - int wc_dilithium_make_key_from_seed(dilithium_key* key, const byte* seed); - int wc_dilithium_export_private(dilithium_key* key, byte* out, word32* outLen); - int wc_dilithium_import_private(const byte* priv, word32 privSz, dilithium_key* key); - int wc_dilithium_export_public(dilithium_key* key, byte* out, word32* outLen); - int wc_dilithium_import_public(const byte* in, word32 inLen, dilithium_key* key); - int wc_dilithium_sign_ctx_msg(const byte* ctx, byte ctxLen, const byte* msg, word32 msgLen, byte* sig, word32* sigLen, dilithium_key* key, WC_RNG* rng); - int wc_dilithium_sign_ctx_msg_with_seed(const byte* ctx, byte ctxLen, const byte* msg, word32 msgLen, byte* sig, word32* sigLen, dilithium_key* key, const byte* seed); - int wc_dilithium_verify_ctx_msg(const byte* sig, word32 sigLen, const byte* ctx, byte ctxLen, const byte* msg, word32 msgLen, int* res, dilithium_key* key); typedef dilithium_key MlDsaKey; - int wc_MlDsaKey_GetPrivLen(MlDsaKey* key, int* len); - int wc_MlDsaKey_GetPubLen(MlDsaKey* key, int* len); - int wc_MlDsaKey_GetSigLen(MlDsaKey* key, int* len); """ - if features["ML_DSA_NO_CTX"]: + + if features["ML_DSA_MAKE_KEY"]: cdef += """ - int wc_dilithium_sign_msg(const byte* msg, word32 msgLen, byte* sig, word32* sigLen, dilithium_key* key, WC_RNG* rng); - int wc_dilithium_sign_msg_with_seed(const byte* msg, word32 msgLen, byte* sig, word32* sigLen, dilithium_key* key, const byte* seed); - int wc_dilithium_verify_msg(const byte* sig, word32 sigLen, const byte* msg, word32 msgLen, int* res, dilithium_key* key); + int wc_dilithium_make_key(dilithium_key* key, WC_RNG* rng); + int wc_dilithium_make_key_from_seed(dilithium_key* key, const byte* seed); """ - ffibuilder.cdef(cdef) + if features["ML_DSA_PRIVATE_KEY"]: + cdef += """ + int wc_dilithium_export_private(dilithium_key* key, byte* out, word32* outLen); + int wc_dilithium_import_private(const byte* priv, word32 privSz, dilithium_key* key); + """ -def main(ffibuilder): - # Default features. + if features["ML_DSA_PUBLIC_KEY"]: + cdef += """ + int wc_dilithium_export_public(dilithium_key* key, byte* out, word32* outLen); + int wc_dilithium_import_public(const byte* in, word32 inLen, dilithium_key* key); + int wc_MlDsaKey_GetPubLen(MlDsaKey* key, int* len); + """ + + if features["ML_DSA_PRIVATE_KEY"] and features["ML_DSA_PUBLIC_KEY"]: + cdef += """ + int wc_MlDsaKey_GetPrivLen(MlDsaKey* key, int* len); + """ + + if features["ML_DSA_SIGN"] or features["ML_DSA_VERIFY"]: + cdef += """ + int wc_MlDsaKey_GetSigLen(MlDsaKey* key, int* len); + """ + + if features["ML_DSA_SIGN"]: + cdef += """ + int wc_dilithium_sign_ctx_msg(const byte* ctx, byte ctxLen, const byte* msg, word32 msgLen, byte* sig, word32* sigLen, dilithium_key* key, WC_RNG* rng); + int wc_dilithium_sign_ctx_msg_with_seed(const byte* ctx, byte ctxLen, const byte* msg, word32 msgLen, byte* sig, word32* sigLen, dilithium_key* key, const byte* seed); + """ + if features["ML_DSA_NO_CTX"]: + cdef += """ + int wc_dilithium_sign_msg(const byte* msg, word32 msgLen, byte* sig, word32* sigLen, dilithium_key* key, WC_RNG* rng); + int wc_dilithium_sign_msg_with_seed(const byte* msg, word32 msgLen, byte* sig, word32* sigLen, dilithium_key* key, const byte* seed); + """ + + if features["ML_DSA_VERIFY"]: + cdef += """ + int wc_dilithium_verify_ctx_msg(const byte* sig, word32 sigLen, const byte* ctx, byte ctxLen, const byte* msg, word32 msgLen, int* res, dilithium_key* key); + """ + if features["ML_DSA_NO_CTX"]: + cdef += """ + int wc_dilithium_verify_msg(const byte* sig, word32 sigLen, const byte* msg, word32 msgLen, int* res, dilithium_key* key); + """ + + return cdef + +def default_features(): + """Return the default features, before detection.""" features = { "MPAPI": 1, "SHA": 1, @@ -1400,6 +1727,46 @@ def main(ffibuilder): "ML_DSA_NO_CTX": 0, "HKDF": 1, "HASHDRBG": 1, + "AES_CTR": 1, + "AES_CBC": 1, + "AES_DECRYPT": 1, + "AESGCM_STREAM_DECRYPT": 1, + "SHA3_224": 1, + "SHA3_256": 1, + "SHA3_384": 1, + "SHA3_512": 1, + "PBKDF2": 1, + "PEM_TO_DER": 1, + "DER_TO_PEM": 1, + "PKCS8": 1, + "RNG": 1, + "RSA_ENCRYPT": 1, + "RSA_PRIVATE": 1, + "RSA_SIGN": 1, + "RSA_VERIFY": 1, + "RSA_OAEP": 1, + "ECC_SIGN": 1, + "ECC_VERIFY": 1, + "ECC_DHE": 1, + "ECC_KEY_IMPORT": 1, + "ECC_KEY_EXPORT": 1, + "ED25519_MAKE_KEY": 1, + "ED25519_SIGN": 1, + "ED25519_VERIFY": 1, + "ED25519_KEY_IMPORT": 1, + "ED25519_KEY_EXPORT": 1, + "ED448_SIGN": 1, + "ED448_VERIFY": 1, + "ED448_KEY_IMPORT": 1, + "ED448_KEY_EXPORT": 1, + "ML_KEM_MAKE_KEY": 1, + "ML_KEM_ENCAPSULATE": 1, + "ML_KEM_DECAPSULATE": 1, + "ML_DSA_MAKE_KEY": 1, + "ML_DSA_SIGN": 1, + "ML_DSA_VERIFY": 1, + "ML_DSA_PUBLIC_KEY": 1, + "ML_DSA_PRIVATE_KEY": 1, } # Ed448 requires SHAKE256, which isn't part of the Windows build, yet. @@ -1408,6 +1775,11 @@ def main(ffibuilder): else: features["ED448"] = 1 + return features + +def main(ffibuilder): + features = default_features() + local_wolfssl = os.environ.get("USE_LOCAL_WOLFSSL") if local_wolfssl: print(f"Using local wolfSSL at {local_wolfssl}.") @@ -1430,7 +1802,10 @@ def main(ffibuilder): ffibuilder = FFI() -main(ffibuilder) + +# cffi's setuptools integration runs this file with __name__ == "__cffi__". +if __name__ in ("__main__", "__cffi__"): + main(ffibuilder) if __name__ == "__main__": ffibuilder.compile(verbose=True) diff --git a/tests/test_aesgcmstream.py b/tests/test_aesgcmstream.py index a587b3b..f93c0a1 100644 --- a/tests/test_aesgcmstream.py +++ b/tests/test_aesgcmstream.py @@ -32,6 +32,10 @@ from binascii import hexlify as b2h from wolfcrypt.ciphers import AesGcmStream + def skip_without_decrypt(): + if not _lib.AESGCM_STREAM_DECRYPT_ENABLED: + pytest.skip("AES-GCM streaming decryption is not compiled in") + def test_encrypt(): """Known answer encrypt-decrypt test with default authentication tag size of 16 bytes""" key = "fedcba9876543210" @@ -42,6 +46,7 @@ def test_encrypt(): assert authTag is not None assert b2h(authTag) == bytes('ac8fcee96dc6ef8e5236da19b6197d2e', 'utf-8') assert b2h(buf) == bytes('5ba7d42e1bf01d7998e932', "utf-8") + skip_without_decrypt() gcmdec = AesGcmStream(key, iv) bufdec = gcmdec.decrypt(buf) gcmdec.final(authTag) @@ -58,6 +63,7 @@ def test_encrypt_short_tag(): assert authTag is not None assert b2h(authTag) == bytes('ac8fcee96dc6ef8e5236da19', 'utf-8') assert b2h(buf) == bytes('5ba7d42e1bf01d7998e932', "utf-8") + skip_without_decrypt() gcmdec = AesGcmStream(key, iv, 12) bufdec = gcmdec.decrypt(buf) gcmdec.final(authTag) @@ -73,6 +79,7 @@ def test_multipart(): assert authTag is not None assert b2h(authTag) == bytes('ac8fcee96dc6ef8e5236da19b6197d2e', 'utf-8') assert b2h(buf) == bytes('5ba7d42e1bf01d7998e932', "utf-8") + skip_without_decrypt() gcmdec = AesGcmStream(key, iv) bufdec = gcmdec.decrypt(buf[:5]) bufdec += gcmdec.decrypt(buf[5:]) @@ -91,6 +98,7 @@ def test_encrypt_aad(): print(b2h(authTag)) assert b2h(authTag) == bytes('8f85338aa0b13f48f8b17482dbb8acca', 'utf-8') assert b2h(buf) == bytes('5ba7d42e1bf01d7998e932', "utf-8") + skip_without_decrypt() gcmdec = AesGcmStream(key, iv) gcmdec.set_aad(aad) bufdec = gcmdec.decrypt(buf) @@ -109,6 +117,7 @@ def test_multipart_aad(): assert authTag is not None assert b2h(authTag) == bytes('8f85338aa0b13f48f8b17482dbb8acca', 'utf-8') assert b2h(buf) == bytes('5ba7d42e1bf01d7998e932', "utf-8") + skip_without_decrypt() gcmdec = AesGcmStream(key, iv) gcmdec.set_aad(aad) bufdec = gcmdec.decrypt(buf[:5]) @@ -117,6 +126,7 @@ def test_multipart_aad(): assert bufdec == t2b("hello world") def test_encrypt_aad_bad(): + skip_without_decrypt() key = "fedcba9876543210" iv = "0123456789abcdef" aad = "aad data" @@ -163,6 +173,7 @@ def test_invalid_tag_bytes(): assert len(tag) == good def test_decrypt_rejects_wrong_tag_length(): + skip_without_decrypt() key = "fedcba9876543210" iv = "0123456789abcdef" gcm = AesGcmStream(key, iv, tag_bytes=16) @@ -200,3 +211,15 @@ def test_repeated_construction_destruction(): gcm.final() del gcm gc.collect() + + def test_decrypt_rejected_when_not_compiled_in(monkeypatch): + """F-12225: streaming decryption needs AES-GCM decryption in the linked wolfSSL.""" + monkeypatch.setattr(_lib, "AESGCM_STREAM_DECRYPT_ENABLED", 0) + key = "fedcba9876543210" + iv = "0123456789abcdef" + gcm = AesGcmStream(key, iv) + with pytest.raises(NotImplementedError, match="AES-GCM streaming decryption"): + gcm.decrypt(bytes.fromhex("5ba7d42e1bf01d7998e932")) + # The rejected call leaves the object usable for encryption. + assert gcm.encrypt("hello world") == bytes.fromhex("5ba7d42e1bf01d7998e932") + assert gcm.final() == bytes.fromhex("ac8fcee96dc6ef8e5236da19b6197d2e") diff --git a/tests/test_asn.py b/tests/test_asn.py index 6904d30..f71ef93 100644 --- a/tests/test_asn.py +++ b/tests/test_asn.py @@ -22,13 +22,19 @@ # ty: ignore[possibly-missing-import] from collections import namedtuple +import importlib.util import pytest import os +from wolfcrypt import asn from wolfcrypt._ffi import lib as _lib from wolfcrypt.utils import h2b if _lib.ASN_ENABLED: - from wolfcrypt.asn import pem_to_der, der_to_pem, make_signature, check_signature + from wolfcrypt.asn import make_signature, check_signature +if _lib.PEM_TO_DER_ENABLED: + from wolfcrypt.asn import pem_to_der +if _lib.DER_TO_PEM_ENABLED: + from wolfcrypt.asn import der_to_pem if _lib.SHA256_ENABLED: from wolfcrypt.hashes import Sha256 if _lib.RSA_ENABLED: @@ -77,7 +83,9 @@ def signature_vectors(): # Signature computed with: # echo -n "wolfcrypt is the best crypto around" | \ # openssl dgst -hex -sha256 -sign tests/certs/server-key.pem - if _lib.ASN_ENABLED and _lib.SHA256_ENABLED and _lib.RSA_ENABLED: + # RSA key objects always create a Random. + if (_lib.PEM_TO_DER_ENABLED and _lib.SHA256_ENABLED and _lib.RSA_ENABLED and _lib.RNG_ENABLED + and _lib.RSA_SIGN_ENABLED and _lib.RSA_VERIFY_ENABLED): vectors.append(TestVector( data="wolfcrypt is the best crypto around", signature=h2b("1d65f21df8fdc9f3c2351792840423481c6b0f2332105abd9248" @@ -99,14 +107,35 @@ def signature_vectors(): def test_pem_der_conversion(pem_der_conversion_vectors): for vector in pem_der_conversion_vectors: - computed_der = pem_to_der(vector.pem, vector.type) - assert computed_der == vector.der + if _lib.PEM_TO_DER_ENABLED: + computed_der = pem_to_der(vector.pem, vector.type) + assert computed_der == vector.der - computed_pem = der_to_pem(vector.der, vector.type) - assert computed_pem == vector.pem + if _lib.DER_TO_PEM_ENABLED: + computed_pem = der_to_pem(vector.der, vector.type) + assert computed_pem == vector.pem def test_signature(signature_vectors): for vector in signature_vectors: assert make_signature(vector.data, vector.hash_cls, vector.priv_key) == vector.signature assert check_signature(vector.signature, vector.data, vector.hash_cls, vector.pub_key) + +def test_pem_der_helpers_defined_only_when_enabled(monkeypatch): + """F-12233: each conversion direction needs its own wolfSSL function.""" + helpers = {"PEM_TO_DER_ENABLED": "pem_to_der", "DER_TO_PEM_ENABLED": "der_to_pem"} + for flag, name in helpers.items(): + assert hasattr(asn, name) == bool(getattr(_lib, flag)), name + + # Load fresh copies of the module as if one direction were not compiled in. + for disabled, disabled_name in helpers.items(): + with monkeypatch.context() as m: + m.setattr(_lib, disabled, 0) + spec = importlib.util.find_spec("wolfcrypt.asn") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + assert not hasattr(module, disabled_name), disabled + for flag, name in helpers.items(): + if flag != disabled: + assert hasattr(module, name) == bool(getattr(_lib, flag)), (disabled, name) + assert hasattr(module, "make_signature") == bool(_lib.ASN_ENABLED), disabled diff --git a/tests/test_build_ffi.py b/tests/test_build_ffi.py new file mode 100644 index 0000000..c0efe2d --- /dev/null +++ b/tests/test_build_ffi.py @@ -0,0 +1,801 @@ +# test_build_ffi.py +# +# Copyright (C) 2006-2026 wolfSSL Inc. +# +# This file is part of wolfSSL. +# +# 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 3 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-1335, USA + +# pylint: disable=redefined-outer-name + +"""Tests for feature detection and cdef generation in scripts/build_ffi.py. + +Nothing here builds wolfSSL or compiles C code. +""" + +import importlib.util +import os +import re +import shlex +import sys + +import pytest +from cffi import FFI + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +BUILD_FFI = os.path.join(ROOT, "scripts", "build_ffi.py") + +pytestmark = pytest.mark.filterwarnings("ignore:The distutils package is deprecated:DeprecationWarning") + +# Sub-capability -> the capability it depends on. Reference configurations +# must enable every sub-capability whenever its parent is enabled. +SUBCAPABILITIES = { + "AES_CTR": "AES", + "AES_CBC": "AES", + "AES_DECRYPT": "AES", + "AESGCM_STREAM_DECRYPT": "AESGCM_STREAM", + "SHA3_224": "SHA3", + "SHA3_256": "SHA3", + "SHA3_384": "SHA3", + "SHA3_512": "SHA3", + "HKDF": "HMAC", + "PBKDF2": "PWDBASED", + "PEM_TO_DER": "ASN", + "DER_TO_PEM": "ASN", + "PKCS8": "ASN", + "RSA_ENCRYPT": "RSA", + "RSA_PRIVATE": "RSA", + "RSA_SIGN": "RSA", + "RSA_VERIFY": "RSA", + "RSA_OAEP": "RSA", + "ECC_SIGN": "ECC", + "ECC_VERIFY": "ECC", + "ECC_DHE": "ECC", + "ECC_KEY_IMPORT": "ECC", + "ECC_KEY_EXPORT": "ECC", + "ED25519_MAKE_KEY": "ED25519", + "ED25519_SIGN": "ED25519", + "ED25519_VERIFY": "ED25519", + "ED25519_KEY_IMPORT": "ED25519", + "ED25519_KEY_EXPORT": "ED25519", + "ED448_SIGN": "ED448", + "ED448_VERIFY": "ED448", + "ED448_KEY_IMPORT": "ED448", + "ED448_KEY_EXPORT": "ED448", + "ML_KEM_MAKE_KEY": "ML_KEM", + "ML_KEM_ENCAPSULATE": "ML_KEM", + "ML_KEM_DECAPSULATE": "ML_KEM", + "ML_DSA_MAKE_KEY": "ML_DSA", + "ML_DSA_SIGN": "ML_DSA", + "ML_DSA_VERIFY": "ML_DSA", + "ML_DSA_PUBLIC_KEY": "ML_DSA", + "ML_DSA_PRIVATE_KEY": "ML_DSA", +} + + +@pytest.fixture(scope="module") +def bf(): + if not os.path.exists(BUILD_FFI): + pytest.skip("scripts/build_ffi.py not available") + spec = importlib.util.spec_from_file_location("wolfcrypt_build_ffi", BUILD_FFI) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +def detect(bf, *defines, fips=False): + return bf.detect_features(list(defines), bf.default_features(), fips) + + +def cdef_for(bf, features): + cdef = bf.make_cdef(features) + FFI().cdef(cdef) # parse only, no compiler + return cdef + + +def test_import_has_no_side_effects(bf): + # set_source() stores cffi's private _assigned_source attribute. + assert not hasattr(bf.ffibuilder, "_assigned_source") + + +def test_flags_declared_and_defined(bf): + features = bf.default_features() + declared = set(re.findall(r"extern int (\w+);", cdef_for(bf, features))) + defined = set(re.findall(r"^\s*int (\w+) = ", bf.make_source(features), re.MULTILINE)) + assert declared + assert declared == defined + + +@pytest.mark.parametrize("config", ["non_fips", "fips_ready", "bundled"]) +def test_reference_configs_enable_all_subcapabilities(bf, config): + if config == "bundled": + path = os.path.join(ROOT, "lib", "wolfssl", bf.get_platform(), bf.version, + "include", "wolfssl", "options.h") + else: + path = os.path.join(ROOT, "windows", config, "user_settings.h") + if not os.path.exists(path): + pytest.skip(f"{path} not available") + with open(path) as f: + features = detect(bf, *f.read().splitlines(), fips=config == "fips_ready") + for sub, parent in SUBCAPABILITIES.items(): + assert features[sub] == features[parent], sub + assert features["RNG"] == 1 + + +# options.h macro -> (feature, its value when the macro is defined) +BASE_FEATURE_MACROS = { + "WOLFSSL_PUBLIC_MP": ("MPAPI", 1), + "NO_SHA": ("SHA", 0), + "NO_SHA256": ("SHA256", 0), + "WOLFSSL_SHA384": ("SHA384", 1), + "WOLFSSL_SHA512": ("SHA512", 1), + "WOLFSSL_SHA3": ("SHA3", 1), + "NO_DES3": ("DES3", 0), + "NO_AES": ("AES", 0), + "WOLFSSL_AES_SIV": ("AES_SIV", 1), + "HAVE_CHACHA": ("CHACHA", 1), + "NO_HMAC": ("HMAC", 0), + "NO_RSA": ("RSA", 0), + "ECC_TIMING_RESISTANT": ("ECC_TIMING_RESISTANCE", 1), + "WC_RSA_BLINDING": ("RSA_BLINDING", 1), + "HAVE_ECC": ("ECC", 1), + "HAVE_ED25519": ("ED25519", 1), + "HAVE_ED448": ("ED448", 1), + "WOLFSSL_KEY_GEN": ("KEYGEN", 1), + "NO_PWDBASED": ("PWDBASED", 0), + "NO_ERROR_STRINGS": ("ERROR_STRINGS", 0), + "NO_ASN": ("ASN", 0), + "WC_RNG_SEED_CB": ("WC_RNG_SEED_CB", 1), + "WOLFSSL_AESGCM_STREAM": ("AESGCM_STREAM", 1), + "WC_RSA_PSS": ("RSA_PSS", 1), + "HAVE_DILITHIUM": ("ML_DSA", 1), + "WOLFSSL_HAVE_MLDSA": ("ML_DSA", 1), + "WOLFSSL_HAVE_MLKEM": ("ML_KEM", 1), + "HAVE_HKDF": ("HKDF", 1), + "WC_NO_HASHDRBG": ("HASHDRBG", 0), + "CUSTOM_RAND_GENERATE_BLOCK": ("HASHDRBG", 0), +} + + +@pytest.mark.parametrize("form", ["#define {}", " # define {}", "#define {} 1", "#define {} /* on */"], + ids=["plain", "indented", "value", "comment"]) +def test_base_features_match_any_define_form(bf, form): + """F-8281: a feature macro counts whatever its indentation, value or comment.""" + for macro, (feature, value) in BASE_FEATURE_MACROS.items(): + assert detect(bf)[feature] == 1 - value, macro + assert detect(bf, form.format(macro))[feature] == value, macro + assert detect(bf, form.format(macro + "_X"))[feature] == 1 - value, macro + assert detect(bf, "/* " + form.format(macro) + " */")[feature] == 1 - value, macro + lines = (form.format("HAVE_CHACHA"), form.format("HAVE_POLY1305")) + assert detect(bf, *lines)["CHACHA20_POLY1305"] == 1 + features = detect(bf, form.format("HAVE_FIPS"), "#define HAVE_FIPS_VERSION 5", fips=True) + assert features["FIPS"] == 1 + assert features["FIPS_VERSION"] == 5 + + +def test_detection_matches_indented_defines_with_values(bf): + assert detect(bf, " # define WOLFSSL_AES_COUNTER 1 /* CTR */")["AES_CTR"] == 1 + assert detect(bf, "/* #define WOLFSSL_AES_COUNTER */")["AES_CTR"] == 0 + assert detect(bf, "#define WOLFSSL_AES_COUNTER_X")["AES_CTR"] == 0 + + +@pytest.mark.skipif(sys.platform == "win32", reason="configure flags are not used on Windows") +@pytest.mark.parametrize("platform", [ + "linux-x86_64", "linux-i686", "linux-aarch64", "linux-armv7l", + "linux-ppc64le", "linux-s390x", "linux-riscv64"]) +def test_make_flags_builds_pic_on_linux(bf, monkeypatch, platform): + """The bundled static wolfSSL is linked into the shared extension, so it + must be position independent on every Linux architecture.""" + monkeypatch.setattr(bf, "get_platform", lambda: platform) + assert "CFLAGS=-fPIC" in shlex.split(bf.make_flags("/prefix", False)) + + +def test_aes_ctr_needs_aes_counter(bf): + features = detect(bf) + assert features["AES_CTR"] == 0 + assert "wc_AesCtrEncrypt" not in cdef_for(bf, features) + + features = detect(bf, "#define WOLFSSL_AES_COUNTER") + assert features["AES_CTR"] == 1 + assert "wc_AesCtrEncrypt" in cdef_for(bf, features) + + features = detect(bf, "#define NO_AES", "#define WOLFSSL_AES_COUNTER") + assert features["AES_CTR"] == 0 + assert "wc_AesCtrEncrypt" not in cdef_for(bf, features) + + +def test_aes_cbc_needs_cbc_support(bf): + features = detect(bf) + assert features["AES_CBC"] == 1 + cdef = cdef_for(bf, features) + assert "wc_AesCbcEncrypt" in cdef + assert "wc_AesCbcDecrypt" in cdef + + for define in ("#define NO_AES_CBC", " #define NO_AES_CBC 1", "#define NO_AES"): + features = detect(bf, define) + assert features["AES_CBC"] == 0, define + cdef = cdef_for(bf, features) + assert "wc_AesCbcEncrypt" not in cdef, define + assert "wc_AesCbcDecrypt" not in cdef, define + + +def test_aes_cbc_decrypt_needs_aes_decrypt(bf): + features = detect(bf) + assert features["AES_DECRYPT"] == 1 + + features = detect(bf, "#define NO_AES_DECRYPT") + assert features["AES_DECRYPT"] == 0 + assert features["AES_CBC"] == 1 + cdef = cdef_for(bf, features) + assert "wc_AesCbcEncrypt" in cdef + assert "wc_AesCbcDecrypt" not in cdef + + assert detect(bf, "#define NO_AES")["AES_DECRYPT"] == 0 + + +def test_aesgcm_stream_decrypt_needs_decrypt_support(bf): + stream = "#define WOLFSSL_AESGCM_STREAM" + decrypt_funcs = ("wc_AesGcmDecryptInit", "wc_AesGcmDecryptUpdate", "wc_AesGcmDecryptFinal") + + features = detect(bf, stream) + assert features["AESGCM_STREAM_DECRYPT"] == 1 + cdef = cdef_for(bf, features) + for name in decrypt_funcs: + assert name in cdef, name + + features = detect(bf, stream, "#define NO_AES_DECRYPT") + assert features["AESGCM_STREAM_DECRYPT"] == 0 + cdef = cdef_for(bf, features) + assert "wc_AesGcmEncryptUpdate" in cdef + assert "wc_AesFree" in cdef + for name in decrypt_funcs: + assert name not in cdef, name + + # aes.c also builds GCM decryption with HAVE_AESGCM_DECRYPT. + features = detect(bf, stream, "#define NO_AES_DECRYPT", "#define HAVE_AESGCM_DECRYPT") + assert features["AESGCM_STREAM_DECRYPT"] == 1 + assert "wc_AesGcmDecryptUpdate" in cdef_for(bf, features) + + assert detect(bf, "#define NO_AES", stream, "#define HAVE_AESGCM_DECRYPT")["AESGCM_STREAM_DECRYPT"] == 0 + assert detect(bf)["AESGCM_STREAM_DECRYPT"] == 0 + + +SHA3_BITS = (224, 256, 384, 512) + + +def sha3_funcs(bits): + return (f"wc_InitSha3_{bits}", f"wc_Sha3_{bits}_Update", f"wc_Sha3_{bits}_Final", + f"wc_Sha3_{bits}_Free", f"wc_Sha3_{bits}_Copy") + + +def test_sha3_variants_follow_nosha3_macros(bf): + sha3 = "#define WOLFSSL_SHA3" + + features = detect(bf, sha3) + cdef = cdef_for(bf, features) + for bits in SHA3_BITS: + assert features[f"SHA3_{bits}"] == 1, bits + for name in sha3_funcs(bits): + assert name in cdef, name + + for disabled in SHA3_BITS: + features = detect(bf, sha3, f"#define WOLFSSL_NOSHA3_{disabled}") + cdef = cdef_for(bf, features) + assert "wc_Sha3;" in cdef + for bits in SHA3_BITS: + enabled = bits != disabled + assert features[f"SHA3_{bits}"] == enabled, (disabled, bits) + for name in sha3_funcs(bits): + assert (name in cdef) == enabled, (disabled, name) + + assert detect(bf, sha3, " #define WOLFSSL_NOSHA3_512 1")["SHA3_512"] == 0 + + # settings.h keeps only SHA3-384 on Xilinx. + for xilinx in ("#define WOLFSSL_XILINX_CRYPT", "#define WOLFSSL_AFALG_XILINX"): + features = detect(bf, sha3, xilinx) + assert [features[f"SHA3_{bits}"] for bits in SHA3_BITS] == [0, 0, 1, 0], xilinx + + features = detect(bf) + for bits in SHA3_BITS: + assert features[f"SHA3_{bits}"] == 0, bits + + +def test_hkdf_needs_hmac(bf): + hkdf_funcs = ("wc_HKDF(", "wc_HKDF_Extract(", "wc_HKDF_Extract_ex(", "wc_HKDF_Expand(", "wc_HKDF_Expand_ex(") + + features = detect(bf, "#define HAVE_HKDF") + assert features["HKDF"] == 1 + cdef = cdef_for(bf, features) + for name in hkdf_funcs: + assert name in cdef, name + + # hmac.h and hmac.c provide HKDF only without NO_HMAC. + features = detect(bf, "#define HAVE_HKDF", "#define NO_HMAC") + assert features["HMAC"] == 0 + assert features["HKDF"] == 0 + cdef = cdef_for(bf, features) + for name in hkdf_funcs: + assert name not in cdef, name + + assert detect(bf)["HKDF"] == 0 + + +def test_pbkdf2_needs_hmac_and_pbkdf2(bf): + features = detect(bf) + assert features["PBKDF2"] == 1 + assert "wc_PBKDF2(" in cdef_for(bf, features) + + # pwdbased.c builds wc_PBKDF2 only with HAVE_PBKDF2 and without NO_HMAC. + for define in ("#define NO_HMAC", "#define NO_PBKDF2", " #define NO_PBKDF2 1", "#define NO_PWDBASED"): + features = detect(bf, define) + assert features["PBKDF2"] == 0, define + assert "wc_PBKDF2(" not in cdef_for(bf, features), define + + # settings.h defines HAVE_PBKDF2 for PKCS7 and scrypt even with NO_PBKDF2. + for define in ("#define HAVE_PBKDF2", "#define HAVE_PKCS7", "#define HAVE_SCRYPT"): + features = detect(bf, "#define NO_PBKDF2", define) + assert features["PBKDF2"] == 1, define + assert "wc_PBKDF2(" in cdef_for(bf, features), define + + assert detect(bf, "#define NO_HMAC", "#define HAVE_PKCS7")["PBKDF2"] == 0 + + +PEM_TO_DER_DECLS = ("DerBuffer", "EncryptedInfo", "wc_PemToDer(", "wc_FreeDer(") + + +def test_pem_der_conversions_follow_their_own_macros(bf): + # settings.h derives WOLFSSL_DER_TO_PEM only from KEY_GEN, CERT_GEN or OPENSSL_EXTRA. + features = detect(bf) + assert features["PEM_TO_DER"] == 1 + assert features["DER_TO_PEM"] == 0 + cdef = cdef_for(bf, features) + assert "wc_EncodeSignature(" in cdef + for name in PEM_TO_DER_DECLS: + assert name in cdef, name + assert "wc_DerToPemEx(" not in cdef + + for define in ("#define WOLFSSL_KEY_GEN", " #define WOLFSSL_KEY_GEN 1", "#define WOLFSSL_CERT_GEN", + "#define OPENSSL_EXTRA", "#define OPENSSL_ALL", "#define WOLFSSL_DUAL_ALG_CERTS", + "#define WOLFSSL_DER_TO_PEM"): + features = detect(bf, define) + assert features["DER_TO_PEM"] == 1, define + assert "wc_DerToPemEx(" in cdef_for(bf, features), define + + features = detect(bf, "#define WOLFSSL_KEY_GEN", "#define WOLFSSL_NO_DER_TO_PEM") + assert features["DER_TO_PEM"] == 0 + assert "wc_DerToPemEx(" not in cdef_for(bf, features) + assert detect(bf, "#define WOLFSSL_CERT_GEN", "#define WOLFSSL_NO_DER_TO_PEM")["DER_TO_PEM"] == 1 + + # settings.h derives WOLFSSL_PEM_TO_DER unless WOLFSSL_NO_PEM or NO_CODING. + for define in ("#define WOLFSSL_NO_PEM", "#define NO_CODING", " #define NO_CODING 1"): + features = detect(bf, "#define WOLFSSL_KEY_GEN", define) + assert features["PEM_TO_DER"] == 0, define + assert features["DER_TO_PEM"] == 1, define + cdef = cdef_for(bf, features) + assert "wc_EncodeSignature(" in cdef, define + assert "wc_DerToPemEx(" in cdef, define + for name in PEM_TO_DER_DECLS: + assert name not in cdef, (define, name) + assert detect(bf, "#define WOLFSSL_NO_PEM", "#define WOLFSSL_PEM_TO_DER")["PEM_TO_DER"] == 1 + + # asn.c builds both directions only with ASN and certificate support. + for define in ("#define NO_ASN", "#define NO_CERTS"): + features = detect(bf, "#define WOLFSSL_KEY_GEN", define) + assert features["PEM_TO_DER"] == 0, define + assert features["DER_TO_PEM"] == 0, define + cdef = cdef_for(bf, features) + for name in (*PEM_TO_DER_DECLS, "wc_DerToPemEx("): + assert name not in cdef, (define, name) + + +def test_pkcs8_offset_needs_pkcs8(bf): + helper = "wc_GetPkcs8TraditionalOffset(" + + features = detect(bf) + assert features["PKCS8"] == 1 + assert helper in cdef_for(bf, features) + + # settings.h defines HAVE_PKCS8 unless both NO_PKCS8 and NO_PKCS12. + for define in ("#define NO_PKCS8", "#define NO_PKCS12"): + features = detect(bf, define) + assert features["PKCS8"] == 1, define + assert helper in cdef_for(bf, features), define + + features = detect(bf, "#define NO_PKCS8", " #define NO_PKCS12 1") + assert features["PKCS8"] == 0 + assert features["RSA"] == 1 + assert helper not in cdef_for(bf, features) + + for define in ("#define HAVE_PKCS8", "#define HAVE_PKCS12"): + assert detect(bf, "#define NO_PKCS8", "#define NO_PKCS12", define)["PKCS8"] == 1, define + + # asn.c builds it only without NO_ASN, even with RSA. + features = detect(bf, "#define NO_ASN") + assert features["RSA"] == 1 + assert features["PKCS8"] == 0 + assert helper not in cdef_for(bf, features) + + +RNG_DECLS = ("wc_InitRng(", "wc_InitRngNonce(", "wc_InitRngNonce_ex(", "wc_RNG_GenerateBlock(", + "wc_RNG_GenerateByte(", "wc_FreeRng(", "wc_RNG_DRBG_Reseed(", "wc_GenerateSeed(", + "wc_SetSeed_Cb(", "wc_RsaSetRNG(") + + +def test_rng_api_needs_rng(bf): + optional = ("#define WC_RNG_SEED_CB", "#define WC_RSA_BLINDING") + + features = detect(bf, *optional) + assert features["RNG"] == 1 + cdef = cdef_for(bf, features) + for name in RNG_DECLS: + assert name in cdef, name + + # random.h replaces the RNG API with macros under WC_NO_RNG. random.c and + # rsa.c then build no RNG, DRBG, seed or RSA blinding functions. + for define in ("#define WC_NO_RNG", " #define WC_NO_RNG 1"): + features = detect(bf, define, *optional) + for name in ("RNG", "HASHDRBG", "WC_RNG_SEED_CB", "RSA_BLINDING"): + assert features[name] == 0, (define, name) + cdef = cdef_for(bf, features) + assert "WC_RNG;" in cdef, define + for name in RNG_DECLS: + assert name not in cdef, (define, name) + + +RSA_OPS = ("wc_RsaPublicEncrypt(", "wc_RsaPublicEncrypt_ex(", "wc_RsaPrivateDecrypt(", + "wc_RsaPrivateDecrypt_ex(", "wc_RsaSSL_Sign(", "wc_RsaSSL_Verify(", "wc_RsaPSS_Sign(", + "wc_MakeRsaKey(") +RSA_SUBSETS = ("RSA_ENCRYPT", "RSA_PRIVATE", "RSA_SIGN", "RSA_VERIFY", "RSA_OAEP") + + +@pytest.mark.parametrize(("defines", "disabled", "absent"), [ + ((), (), ()), + # --enable-rsapub + (("#define WOLFSSL_RSA_PUBLIC_ONLY",), ("RSA_PRIVATE", "RSA_SIGN"), + ("wc_RsaPrivateDecrypt(", "wc_RsaPrivateDecrypt_ex(", "wc_RsaSSL_Sign(", "wc_RsaPSS_Sign(", + "wc_MakeRsaKey(")), + (("#define WOLFSSL_RSA_VERIFY_ONLY",), ("RSA_ENCRYPT", "RSA_SIGN"), + ("wc_RsaPublicEncrypt(", "wc_RsaPublicEncrypt_ex(", "wc_RsaSSL_Sign(", "wc_RsaPSS_Sign(")), + (("#define WOLFSSL_RSA_VERIFY_INLINE",), ("RSA_VERIFY",), ("wc_RsaSSL_Verify(",)), + # --enable-rsavfy + (("#define WOLFSSL_RSA_PUBLIC_ONLY", "#define WOLFSSL_RSA_VERIFY_ONLY", "#define WOLFSSL_RSA_VERIFY_INLINE"), + ("RSA_ENCRYPT", "RSA_PRIVATE", "RSA_SIGN", "RSA_VERIFY"), RSA_OPS), + # --disable-oaep + (("#define WC_NO_RSA_OAEP",), ("RSA_OAEP",), ("wc_RsaPublicEncrypt_ex(", "wc_RsaPrivateDecrypt_ex(")), + ((" #define WC_NO_RSA_OAEP 1",), ("RSA_OAEP",), ("wc_RsaPublicEncrypt_ex(", "wc_RsaPrivateDecrypt_ex(")), +], ids=["default", "public-only", "verify-only", "verify-inline", "rsavfy", "no-oaep", "no-oaep-indented"]) +def test_rsa_operations_follow_subset_macros(bf, defines, disabled, absent): + features = detect(bf, "#define WOLFSSL_KEY_GEN", "#define WC_RSA_PSS", *defines) + for name in RSA_SUBSETS: + assert features[name] == (name not in disabled), name + cdef = cdef_for(bf, features) + for name in RSA_OPS: + assert (name in cdef) == (name not in absent), name + # Key decoding and encoding and PSS verification stay available. + for name in ("wc_RsaPublicKeyDecode(", "wc_RsaPrivateKeyDecode(", "wc_RsaKeyToDer(", + "wc_RsaKeyToPublicDer(", "wc_RsaPSS_Verify(", "wc_RsaPSS_CheckPadding("): + assert name in cdef, name + + +def test_rsa_subsets_need_rsa(bf): + features = detect(bf, "#define NO_RSA") + for name in RSA_SUBSETS: + assert features[name] == 0, name + cdef = cdef_for(bf, features) + for name in RSA_OPS: + assert name not in cdef, name + + +ECC_SUBSETS = ("ECC_SIGN", "ECC_VERIFY", "ECC_DHE", "ECC_KEY_IMPORT", "ECC_KEY_EXPORT") +ECC_OPS = { + "ECC_SIGN": ("wc_ecc_sign_hash(", "wc_ecc_sign_hash_ex("), + "ECC_VERIFY": ("wc_ecc_verify_hash(", "wc_ecc_verify_hash_ex("), + "ECC_DHE": ("wc_ecc_shared_secret(",), + "ECC_KEY_IMPORT": ("wc_EccPrivateKeyDecode(", "wc_EccPublicKeyDecode(", "wc_ecc_import_x963(", + "wc_ecc_import_unsigned("), + "ECC_KEY_EXPORT": ("wc_EccKeyToDer(", "wc_EccKeyDerSize(", "wc_EccPublicKeyToDer(", + "wc_ecc_export_x963(", "wc_ecc_export_private_raw(", "wc_ecc_export_public_raw("), +} +ECC_COMMON = ("ecc_key;", "wc_ecc_init(", "wc_ecc_free(", "wc_ecc_make_key(", "wc_ecc_size(", + "wc_ecc_sig_size(", "wc_ecc_get_curve_size_from_id(", "wc_ecc_check_key(") + + +@pytest.mark.parametrize(("defines", "disabled"), [ + ((), ()), + (("#define NO_ECC_SIGN",), ("ECC_SIGN",)), + ((" #define NO_ECC_SIGN 1",), ("ECC_SIGN",)), + (("#define NO_ECC_VERIFY",), ("ECC_VERIFY",)), + (("#define NO_ECC_DHE",), ("ECC_DHE",)), + (("#define NO_ECC_KEY_IMPORT",), ("ECC_KEY_IMPORT",)), + (("#define NO_ECC_KEY_EXPORT",), ("ECC_KEY_EXPORT",)), + # settings.h: key export needs WOLFSSL_SP_MATH or big integer math. + (("#define NO_BIG_INT",), ("ECC_KEY_EXPORT",)), + (("#define NO_BIG_INT", "#define WOLFSSL_SP_MATH"), ()), + # settings.h: DHE and timing resistant signing need the RNG. + (("#define WC_NO_RNG",), ("ECC_DHE",)), + (("#define WC_NO_RNG", "#define ECC_TIMING_RESISTANT"), ("ECC_SIGN", "ECC_DHE")), + (("#define NO_ECC_SIGN", "#define NO_ECC_DHE"), ("ECC_SIGN", "ECC_DHE")), +], ids=["default", "no-sign", "no-sign-indented", "no-verify", "no-dhe", "no-import", "no-export", + "no-big-int", "no-big-int-sp-math", "no-rng", "no-rng-timing-resistant", "no-sign-no-dhe"]) +def test_ecc_operations_follow_subset_macros(bf, defines, disabled): + features = detect(bf, "#define HAVE_ECC", "#define WOLFSSL_PUBLIC_MP", *defines) + assert features["ECC"] == 1 + for name in ECC_SUBSETS: + assert features[name] == (name not in disabled), name + cdef = cdef_for(bf, features) + for subset, names in ECC_OPS.items(): + for name in names: + assert (name in cdef) == (subset not in disabled), name + for name in ECC_COMMON: + assert name in cdef, name + + +def test_ecc_subsets_need_ecc(bf): + features = detect(bf, "#define WOLFSSL_PUBLIC_MP") + assert features["ECC"] == 0 + for name in ECC_SUBSETS: + assert features[name] == 0, name + cdef = cdef_for(bf, features) + for names in ECC_OPS.values(): + for name in names: + assert name not in cdef, name + + +def test_ecc_digest_bounds_without_rsa(bf): + """F-8279: ECDSA checks digest sizes against wolfSSL's bounds, also without RSA.""" + features = detect(bf, "#define HAVE_ECC", "#define NO_RSA") + assert features["ECC"] == 1 + assert features["RSA"] == 0 + cdef = cdef_for(bf, features) + for name in ("WC_MIN_DIGEST_SIZE", "WC_MAX_DIGEST_SIZE"): + assert name in cdef, name + + +ED25519_SUBSETS = ("ED25519_MAKE_KEY", "ED25519_SIGN", "ED25519_VERIFY", "ED25519_KEY_IMPORT", + "ED25519_KEY_EXPORT") +ED25519_OPS = { + "ED25519_MAKE_KEY": ("wc_ed25519_make_key(", "wc_ed25519_make_public("), + "ED25519_SIGN": ("wc_ed25519_sign_msg(",), + "ED25519_VERIFY": ("wc_ed25519_verify_msg(",), + "ED25519_KEY_IMPORT": ("wc_Ed25519PrivateKeyDecode(", "wc_Ed25519PublicKeyDecode(", + "wc_ed25519_import_public(", "wc_ed25519_import_private_only(", + "wc_ed25519_import_private_key("), + "ED25519_KEY_EXPORT": ("wc_Ed25519KeyToDer(", "wc_Ed25519PublicKeyToDer(", "wc_ed25519_export_public(", + "wc_ed25519_export_private_only(", "wc_ed25519_export_private(", + "wc_ed25519_export_key("), +} +ED25519_COMMON = ("ed25519_key;", "wc_ed25519_init(", "wc_ed25519_free(", "wc_ed25519_size(", + "wc_ed25519_sig_size(", "wc_ed25519_check_key(", "wc_ed25519_pub_size(", + "wc_ed25519_priv_size(") + + +@pytest.mark.parametrize(("defines", "disabled"), [ + ((), ()), + (("#define NO_ED25519_SIGN", "#define NO_ED25519_MAKE_KEY"), ("ED25519_SIGN", "ED25519_MAKE_KEY")), + (("#define NO_ED25519_SIGN",), ("ED25519_SIGN",)), + ((" #define NO_ED25519_SIGN 1",), ("ED25519_SIGN",)), + (("#define NO_ED25519_VERIFY",), ("ED25519_VERIFY",)), + (("#define NO_ED25519_KEY_IMPORT",), ("ED25519_KEY_IMPORT",)), + (("#define NO_ED25519_KEY_EXPORT",), ("ED25519_KEY_EXPORT",)), +], ids=["default", "no-sign-no-make-key", "no-sign", "no-sign-indented", "no-verify", "no-import", + "no-export"]) +def test_ed25519_operations_follow_subset_macros(bf, defines, disabled): + features = detect(bf, "#define HAVE_ED25519", *defines) + assert features["ED25519"] == 1 + for name in ED25519_SUBSETS: + assert features[name] == (name not in disabled), name + cdef = cdef_for(bf, features) + for subset, names in ED25519_OPS.items(): + for name in names: + assert (name in cdef) == (subset not in disabled), name + for name in ED25519_COMMON: + assert name in cdef, name + + +def test_ed25519_subsets_need_ed25519(bf): + features = detect(bf) + assert features["ED25519"] == 0 + for name in ED25519_SUBSETS: + assert features[name] == 0, name + cdef = cdef_for(bf, features) + for names in ED25519_OPS.values(): + for name in names: + assert name not in cdef, name + + +ED448_SUBSETS = ("ED448_SIGN", "ED448_VERIFY", "ED448_KEY_IMPORT", "ED448_KEY_EXPORT") +ED448_OPS = { + "ED448_SIGN": ("wc_ed448_sign_msg(",), + "ED448_VERIFY": ("wc_ed448_verify_msg(",), + "ED448_KEY_IMPORT": ("wc_Ed448PrivateKeyDecode(", "wc_Ed448PublicKeyDecode(", "wc_ed448_import_public(", + "wc_ed448_import_private_only(", "wc_ed448_import_private_key("), + "ED448_KEY_EXPORT": ("wc_Ed448KeyToDer(", "wc_Ed448PublicKeyToDer(", "wc_ed448_export_public(", + "wc_ed448_export_private_only(", "wc_ed448_export_private(", "wc_ed448_export_key("), +} +# wolfSSL builds Ed448 key generation unconditionally. +ED448_COMMON = ("ed448_key;", "wc_ed448_init(", "wc_ed448_free(", "wc_ed448_make_key(", "wc_ed448_make_public(", + "wc_ed448_size(", "wc_ed448_sig_size(", "wc_ed448_check_key(", "wc_ed448_pub_size(", + "wc_ed448_priv_size(") + + +@pytest.mark.parametrize(("defines", "disabled"), [ + ((), ()), + (("#define NO_ED448_SIGN",), ("ED448_SIGN",)), + ((" #define NO_ED448_SIGN 1",), ("ED448_SIGN",)), + (("#define NO_ED448_VERIFY",), ("ED448_VERIFY",)), + (("#define NO_ED448_KEY_IMPORT",), ("ED448_KEY_IMPORT",)), + (("#define NO_ED448_KEY_EXPORT",), ("ED448_KEY_EXPORT",)), + (("#define NO_ED448_SIGN", "#define NO_ED448_KEY_EXPORT"), ("ED448_SIGN", "ED448_KEY_EXPORT")), +], ids=["default", "no-sign", "no-sign-indented", "no-verify", "no-import", "no-export", "no-sign-no-export"]) +def test_ed448_operations_follow_subset_macros(bf, defines, disabled): + features = detect(bf, "#define HAVE_ED448", *defines) + assert features["ED448"] == 1 + for name in ED448_SUBSETS: + assert features[name] == (name not in disabled), name + cdef = cdef_for(bf, features) + for subset, names in ED448_OPS.items(): + for name in names: + assert (name in cdef) == (subset not in disabled), name + for name in ED448_COMMON: + assert name in cdef, name + + +def test_ed448_subsets_need_ed448(bf): + features = detect(bf) + assert features["ED448"] == 0 + for name in ED448_SUBSETS: + assert features[name] == 0, name + cdef = cdef_for(bf, features) + for names in ED448_OPS.values(): + for name in names: + assert name not in cdef, name + + +ML_KEM_SUBSETS = ("ML_KEM_MAKE_KEY", "ML_KEM_ENCAPSULATE", "ML_KEM_DECAPSULATE") +ML_KEM_OPS = { + "ML_KEM_MAKE_KEY": ("wc_KyberKey_MakeKey(", "wc_KyberKey_MakeKeyWithRandom("), + "ML_KEM_ENCAPSULATE": ("wc_KyberKey_Encapsulate(", "wc_KyberKey_EncapsulateWithRandom("), + "ML_KEM_DECAPSULATE": ("wc_KyberKey_Decapsulate(",), +} +# wolfSSL builds key setup, sizes and encoding for every operation subset. +ML_KEM_COMMON = ("KyberKey;", "wc_KyberKey_Init(", "wc_KyberKey_Free(", "wc_KyberKey_CipherTextSize(", + "wc_KyberKey_SharedSecretSize(", "wc_KyberKey_PrivateKeySize(", "wc_KyberKey_PublicKeySize(", + "wc_KyberKey_EncodePublicKey(", "wc_KyberKey_DecodePublicKey(", "wc_KyberKey_EncodePrivateKey(", + "wc_KyberKey_DecodePrivateKey(") + + +@pytest.mark.parametrize(("defines", "disabled"), [ + ((), ()), + (("#define WOLFSSL_MLKEM_NO_MAKE_KEY",), ("ML_KEM_MAKE_KEY",)), + ((" #define WOLFSSL_MLKEM_NO_MAKE_KEY 1",), ("ML_KEM_MAKE_KEY",)), + (("#define WOLFSSL_MLKEM_NO_ENCAPSULATE",), ("ML_KEM_ENCAPSULATE",)), + (("#define WOLFSSL_MLKEM_NO_DECAPSULATE",), ("ML_KEM_DECAPSULATE",)), + (("#define WOLFSSL_KYBER_NO_MAKE_KEY",), ("ML_KEM_MAKE_KEY",)), + (("#define WOLFSSL_KYBER_NO_ENCAPSULATE",), ("ML_KEM_ENCAPSULATE",)), + (("#define WOLFSSL_KYBER_NO_DECAPSULATE",), ("ML_KEM_DECAPSULATE",)), + (("#define WOLFSSL_MLKEM_NO_MAKE_KEY", "#define WOLFSSL_MLKEM_NO_DECAPSULATE"), + ("ML_KEM_MAKE_KEY", "ML_KEM_DECAPSULATE")), +], ids=["default", "no-make-key", "no-make-key-indented", "no-encapsulate", "no-decapsulate", + "legacy-no-make-key", "legacy-no-encapsulate", "legacy-no-decapsulate", "encapsulate-only"]) +def test_ml_kem_operations_follow_subset_macros(bf, defines, disabled): + features = detect(bf, "#define WOLFSSL_HAVE_MLKEM", *defines) + assert features["ML_KEM"] == 1 + for name in ML_KEM_SUBSETS: + assert features[name] == (name not in disabled), name + cdef = cdef_for(bf, features) + for subset, names in ML_KEM_OPS.items(): + for name in names: + assert (name in cdef) == (subset not in disabled), name + for name in ML_KEM_COMMON: + assert name in cdef, name + + +def test_ml_kem_subsets_need_ml_kem(bf): + features = detect(bf) + assert features["ML_KEM"] == 0 + for name in ML_KEM_SUBSETS: + assert features[name] == 0, name + cdef = cdef_for(bf, features) + for names in ML_KEM_OPS.values(): + for name in names: + assert name not in cdef, name + + +ML_DSA_SUBSETS = ("ML_DSA_MAKE_KEY", "ML_DSA_SIGN", "ML_DSA_VERIFY", "ML_DSA_PUBLIC_KEY", "ML_DSA_PRIVATE_KEY") +# Function -> sub-capabilities that must all be enabled for it. +ML_DSA_OPS = { + "wc_dilithium_make_key(": ("ML_DSA_MAKE_KEY",), + "wc_dilithium_make_key_from_seed(": ("ML_DSA_MAKE_KEY",), + "wc_dilithium_export_public(": ("ML_DSA_PUBLIC_KEY",), + "wc_dilithium_import_public(": ("ML_DSA_PUBLIC_KEY",), + "wc_MlDsaKey_GetPubLen(": ("ML_DSA_PUBLIC_KEY",), + "wc_dilithium_export_private(": ("ML_DSA_PRIVATE_KEY",), + "wc_dilithium_import_private(": ("ML_DSA_PRIVATE_KEY",), + "wc_MlDsaKey_GetPrivLen(": ("ML_DSA_PRIVATE_KEY", "ML_DSA_PUBLIC_KEY"), + "wc_dilithium_sign_ctx_msg(": ("ML_DSA_SIGN",), + "wc_dilithium_sign_ctx_msg_with_seed(": ("ML_DSA_SIGN",), + "wc_dilithium_verify_ctx_msg(": ("ML_DSA_VERIFY",), +} +ML_DSA_COMMON = ("dilithium_key;", "wc_dilithium_init_ex(", "wc_dilithium_set_level(", "wc_dilithium_free(", + "DILITHIUM_SEED_SZ;", "WC_ML_DSA_44;") +MLDSA_VERIFY_ONLY = ("ML_DSA_MAKE_KEY", "ML_DSA_SIGN", "ML_DSA_PRIVATE_KEY") + + +@pytest.mark.parametrize(("defines", "disabled"), [ + ((), ()), + (("#define WOLFSSL_MLDSA_VERIFY_ONLY",), MLDSA_VERIFY_ONLY), + ((" #define WOLFSSL_MLDSA_VERIFY_ONLY 1",), MLDSA_VERIFY_ONLY), + (("#define WOLFSSL_MLDSA_VERIFY_ONLY", "#define WOLFSSL_MLDSA_NO_MAKE_KEY", "#define WOLFSSL_MLDSA_NO_SIGN"), + MLDSA_VERIFY_ONLY), + (("#define WOLFSSL_MLDSA_NO_MAKE_KEY", "#define WOLFSSL_MLDSA_NO_SIGN"), MLDSA_VERIFY_ONLY), + (("#define WOLFSSL_DILITHIUM_VERIFY_ONLY",), MLDSA_VERIFY_ONLY), + (("#define WOLFSSL_MLDSA_NO_MAKE_KEY",), ("ML_DSA_MAKE_KEY",)), + (("#define WOLFSSL_MLDSA_NO_SIGN",), ("ML_DSA_SIGN",)), + (("#define WOLFSSL_DILITHIUM_NO_SIGN",), ("ML_DSA_SIGN",)), + (("#define WOLFSSL_MLDSA_NO_VERIFY",), ("ML_DSA_VERIFY",)), + (("#define WOLFSSL_DILITHIUM_NO_VERIFY",), ("ML_DSA_VERIFY",)), + (("#define WOLFSSL_MLDSA_NO_MAKE_KEY", "#define WOLFSSL_MLDSA_NO_VERIFY"), + ("ML_DSA_MAKE_KEY", "ML_DSA_VERIFY", "ML_DSA_PUBLIC_KEY")), + (("#define WOLFSSL_MLDSA_NO_MAKE_KEY", "#define WOLFSSL_MLDSA_NO_VERIFY", "#define WOLFSSL_MLDSA_PUBLIC_KEY"), + ("ML_DSA_MAKE_KEY", "ML_DSA_VERIFY")), + (("#define WOLFSSL_MLDSA_VERIFY_ONLY", "#define WOLFSSL_DILITHIUM_PRIVATE_KEY"), + ("ML_DSA_MAKE_KEY", "ML_DSA_SIGN")), +], ids=["default", "verify-only", "verify-only-indented", "verify-only-configure", "verify-only-derived", + "legacy-verify-only", "no-make-key", "no-sign", "legacy-no-sign", "no-verify", "legacy-no-verify", + "sign-only", "sign-only-public-key", "verify-only-private-key"]) +@pytest.mark.parametrize("parent", ["#define WOLFSSL_HAVE_MLDSA", "#define HAVE_DILITHIUM"]) +def test_ml_dsa_operations_follow_subset_macros(bf, parent, defines, disabled): + features = detect(bf, parent, *defines) + assert features["ML_DSA"] == 1 + for name in ML_DSA_SUBSETS: + assert features[name] == (name not in disabled), name + cdef = cdef_for(bf, features) + for name, needs in ML_DSA_OPS.items(): + assert (name in cdef) == all(features[n] for n in needs), name + assert ("wc_MlDsaKey_GetSigLen(" in cdef) == bool(features["ML_DSA_SIGN"] or features["ML_DSA_VERIFY"]) + for name in ML_DSA_COMMON: + assert name in cdef, name + + +@pytest.mark.parametrize(("defines", "sign", "verify"), [ + ((), True, True), + (("#define WOLFSSL_MLDSA_VERIFY_ONLY",), False, True), + (("#define WOLFSSL_MLDSA_NO_VERIFY",), True, False), +], ids=["default", "verify-only", "no-verify"]) +def test_ml_dsa_no_ctx_operations_follow_subset_macros(bf, defines, sign, verify): + features = detect(bf, "#define WOLFSSL_MLDSA_NO_CTX", "#define WOLFSSL_HAVE_MLDSA", *defines) + assert features["ML_DSA_NO_CTX"] == 1 + cdef = cdef_for(bf, features) + assert ("wc_dilithium_sign_msg(" in cdef) == sign + assert ("wc_dilithium_sign_msg_with_seed(" in cdef) == sign + assert ("wc_dilithium_verify_msg(" in cdef) == verify + + +@pytest.mark.parametrize("name", ["WOLFSSL_MLDSA_NO_CTX", "WOLFSSL_MLDSA_FIPS204_DRAFT", + "WOLFSSL_DILITHIUM_NO_CTX", "WOLFSSL_DILITHIUM_FIPS204_DRAFT"]) +def test_ml_dsa_no_ctx_macro_on_last_line(bf, name): + """F-13023: the no-context macro is found when nothing follows it.""" + assert detect(bf, "#define WOLFSSL_HAVE_MLDSA", f"#define {name}")["ML_DSA_NO_CTX"] == 1 + assert detect(bf, "#define WOLFSSL_HAVE_MLDSA", f"#define {name}_X")["ML_DSA_NO_CTX"] == 0 + + +def test_ml_dsa_subsets_need_ml_dsa(bf): + features = detect(bf) + assert features["ML_DSA"] == 0 + for name in ML_DSA_SUBSETS: + assert features[name] == 0, name + cdef = cdef_for(bf, features) + for name in (*ML_DSA_OPS, "wc_MlDsaKey_GetSigLen("): + assert name not in cdef, name diff --git a/tests/test_chacha_iv.py b/tests/test_chacha_iv.py index b1006d9..24f8fea 100644 --- a/tests/test_chacha_iv.py +++ b/tests/test_chacha_iv.py @@ -82,3 +82,34 @@ def test_failed_set_iv_keeps_encrypt_blocked(monkeypatch): # encrypt() would instead run with a stale IV. with pytest.raises(WolfCryptError): cipher.encrypt(b"A" * 16) + + +def test_invalid_nonce_length_disarms_cipher(): + """ + F-10069: a set_iv() that fails on nonce length must not leave the cipher + usable with the rejected nonce, even after an earlier successful set_iv(). + """ + cipher = ChaCha(KEY) + cipher.set_iv(NONCE) + + with pytest.raises(ValueError): + cipher.set_iv(b"\x02" * 5) + + with pytest.raises(WolfCryptError): + cipher.encrypt(b"A" * 16) + with pytest.raises(WolfCryptError): + cipher.decrypt(b"A" * 16) + + +def test_set_iv_after_invalid_nonce_rearms(): + enc = ChaCha(KEY) + enc.set_iv(NONCE) + with pytest.raises(ValueError): + enc.set_iv(b"\x02" * 5) + enc.set_iv(NONCE) + plaintext = b"the quick brown fox" + ciphertext = enc.encrypt(plaintext) + + dec = ChaCha(KEY) + dec.set_iv(NONCE) + assert dec.decrypt(ciphertext) == plaintext diff --git a/tests/test_ciphers.py b/tests/test_ciphers.py index ffbf29c..6ad34d5 100644 --- a/tests/test_ciphers.py +++ b/tests/test_ciphers.py @@ -21,19 +21,32 @@ # pylint: disable=redefined-outer-name # ty: ignore[possibly-missing-import] +import hashlib +import importlib.util import os import random from collections import namedtuple import pytest +from wolfcrypt import ciphers from wolfcrypt._ffi import lib as _lib from wolfcrypt.ciphers import MODE_CBC, MODE_CTR, MODE_ECB, WolfCryptError +from wolfcrypt.exceptions import WolfCryptApiError from wolfcrypt.random import Random from wolfcrypt.utils import h2b, t2b certs_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "certs") + +def load_fresh_ciphers(): + """Load a fresh copy of wolfcrypt.ciphers, e.g. with _lib flags patched.""" + spec = importlib.util.find_spec("wolfcrypt.ciphers") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + if _lib.DES3_ENABLED: from wolfcrypt.ciphers import Des3 @@ -50,7 +63,7 @@ from wolfcrypt.ciphers import HASH_TYPE_SHA, HASH_TYPE_SHA256, RsaPrivate, RsaPublic if _lib.ECC_ENABLED: - from wolfcrypt.ciphers import EccPrivate, EccPublic + from wolfcrypt.ciphers import ECC_SECP112R1, ECC_SECP128R1, ECC_SECP160R1, ECC_SECP192R1, EccPrivate, EccPublic if _lib.ED25519_ENABLED: from wolfcrypt.ciphers import Ed25519Private, Ed25519Public @@ -244,9 +257,13 @@ def test_block_cipher(cipher_cls, vectors): plaintext = vectors[cipher_cls].plaintext ciphertext = vectors[cipher_cls].ciphertext ciphertext_ctr = vectors[cipher_cls].ciphertext_ctr + is_aes = _lib.AES_ENABLED and cipher_cls is Aes + # Without AES-CBC, MODE_CBC is rejected before its arguments are checked. + cbc = not (is_aes and not _lib.AES_CBC_ENABLED) - with pytest.raises(ValueError): - cipher_cls.new(key[:-1], MODE_CBC, iv) # invalid key length + if cbc: + with pytest.raises(ValueError): + cipher_cls.new(key[:-1], MODE_CBC, iv) # invalid key length with pytest.raises(ValueError): cipher_cls.new(key, -1, iv) # invalid mode @@ -254,21 +271,25 @@ def test_block_cipher(cipher_cls, vectors): with pytest.raises(ValueError): cipher_cls.new(key, MODE_ECB, iv) # unsuported mode - with pytest.raises(ValueError): - cipher_cls.new(key, MODE_CBC, None) # invalid iv + if cbc: + with pytest.raises(ValueError): + cipher_cls.new(key, MODE_CBC, None) # invalid iv - with pytest.raises(ValueError): - cipher_cls.new(key, MODE_CBC, iv[:-1]) # invalid iv length + with pytest.raises(ValueError): + cipher_cls.new(key, MODE_CBC, iv[:-1]) # invalid iv length # Test AES in counter mode - if ciphertext_ctr is not None: + if ciphertext_ctr is not None and _lib.AES_CTR_ENABLED: cipher_obj = cipher_cls.new(key, MODE_CTR, iv) res = cipher_obj.encrypt(plaintext) assert res == ciphertext_ctr cipher_obj = cipher_cls.new(key, MODE_CTR, iv) assert plaintext == cipher_obj.decrypt(res) + if not cbc: + return + # single encryption cipher_obj = cipher_new(cipher_cls, vectors) @@ -287,6 +308,9 @@ def test_block_cipher(cipher_cls, vectors): assert result == ciphertext + if is_aes and not _lib.AES_DECRYPT_ENABLED: + return + # single decryption cipher_obj = cipher_new(cipher_cls, vectors) @@ -326,6 +350,14 @@ def test_chacha_enc_dec(chacha_obj, vectors): assert plaintext == dec if _lib.RSA_ENABLED: + # RSA encrypt and sign create a Random on first use. + needs_rng = pytest.mark.skipif(not _lib.RNG_ENABLED, reason="RNG not enabled") + needs_encrypt_decrypt = pytest.mark.skipif(not (_lib.RSA_ENCRYPT_ENABLED and _lib.RSA_PRIVATE_ENABLED), + reason="RSA encryption or decryption not enabled") + needs_oaep = pytest.mark.skipif(not _lib.RSA_OAEP_ENABLED, reason="RSA OAEP not enabled") + needs_sign_verify = pytest.mark.skipif(not (_lib.RSA_SIGN_ENABLED and _lib.RSA_VERIFY_ENABLED), + reason="RSA signing or verification not enabled") + @pytest.fixture def rng(): return Random() @@ -390,6 +422,7 @@ def rsa_public_pem_rng(vectors, rng): pem = f.read() return RsaPublic.from_pem(pem, rng=rng) # ty: ignore[possibly-missing-attribute] + @needs_rng def test_new_rsa_raises(vectors): with pytest.raises(WolfCryptError): RsaPrivate(vectors[RsaPrivate].key[:-1]) # invalid key length @@ -397,11 +430,13 @@ def test_new_rsa_raises(vectors): with pytest.raises(WolfCryptError): RsaPublic(vectors[RsaPublic].key[:-1]) # invalid key length - if _lib.KEYGEN_ENABLED: + if _lib.KEYGEN_ENABLED and _lib.RSA_PRIVATE_ENABLED: with pytest.raises(WolfCryptError): # invalid key size RsaPrivate.make_key(16384) # ty: ignore[possibly-missing-attribute] + @needs_rng + @needs_encrypt_decrypt def test_rsa_encrypt_decrypt(rsa_private, rsa_public): plaintext = t2b("Everyone gets Friday off.") @@ -418,6 +453,8 @@ def test_rsa_encrypt_decrypt(rsa_private, rsa_public): assert 1024 / 8 == len(ciphertext) == rsa_private.output_size assert plaintext == rsa_private.decrypt(ciphertext) + @needs_rng + @needs_encrypt_decrypt def test_rsa_encrypt_decrypt_rng(rsa_private_rng, rsa_public_rng): plaintext = t2b("Everyone gets Friday off.") @@ -434,6 +471,9 @@ def test_rsa_encrypt_decrypt_rng(rsa_private_rng, rsa_public_rng): assert 1024 / 8 == len(ciphertext) == rsa_private_rng.output_size assert plaintext == rsa_private_rng.decrypt(ciphertext) + @needs_rng + @needs_encrypt_decrypt + @needs_oaep def test_rsa_encrypt_decrypt_pad_oaep(rsa_private_oaep, rsa_public_oaep): plaintext = t2b("Everyone gets Friday off.") @@ -451,6 +491,9 @@ def test_rsa_encrypt_decrypt_pad_oaep(rsa_private_oaep, rsa_public_oaep): assert plaintext == rsa_private_oaep.decrypt_oaep(ciphertext) + @pytest.mark.skipif(not _lib.PKCS8_ENABLED, reason="PKCS#8 not enabled") + @needs_rng + @needs_encrypt_decrypt def test_rsa_pkcs8_encrypt_decrypt(rsa_private_pkcs8, rsa_public): plaintext = t2b("Everyone gets Friday off.") @@ -468,6 +511,8 @@ def test_rsa_pkcs8_encrypt_decrypt(rsa_private_pkcs8, rsa_public): assert plaintext == rsa_private_pkcs8.decrypt(ciphertext) + @needs_rng + @needs_sign_verify def test_rsa_sign_verify(rsa_private, rsa_public): plaintext = t2b("Everyone gets Friday off.") @@ -484,7 +529,26 @@ def test_rsa_sign_verify(rsa_private, rsa_public): assert 1024 / 8 == len(signature) == rsa_private.output_size assert plaintext == rsa_private.verify(signature) + @needs_rng + @needs_sign_verify + def test_rsa_verify_without_rng(rsa_private, vectors, monkeypatch): + plaintext = t2b("Everyone gets Friday off.") + signature = rsa_private.sign(plaintext) + + def no_rng(): + raise NotImplementedError("RNG is not supported by this wolfSSL build") + + # Verify must not need an RNG. Blinding always needs one. + monkeypatch.setattr(ciphers, "Random", no_rng) + monkeypatch.setattr(_lib, "RSA_BLINDING_ENABLED", 0) + rsa_public = RsaPublic(vectors[RsaPublic].key) + assert plaintext == rsa_public.verify(signature) + with pytest.raises(NotImplementedError): + _ = rsa_public._random + if _lib.RSA_PSS_ENABLED: + @needs_rng + @pytest.mark.skipif(not _lib.RSA_SIGN_ENABLED, reason="RSA signing not enabled") def test_rsa_pss_sign_verify(rsa_private_pss, rsa_public_pss): plaintext = t2b("Everyone gets Friday off.") @@ -501,6 +565,9 @@ def test_rsa_pss_sign_verify(rsa_private_pss, rsa_public_pss): assert 1024 / 8 == len(signature) == rsa_private_pss.output_size assert rsa_private_pss.verify_pss(plaintext, signature) is True + @pytest.mark.skipif(not _lib.PEM_TO_DER_ENABLED, reason="PEM to DER not enabled") + @needs_rng + @needs_sign_verify def test_rsa_sign_verify_pem(rsa_private_pem, rsa_public_pem): plaintext = t2b("Everyone gets Friday off.") @@ -517,6 +584,9 @@ def test_rsa_sign_verify_pem(rsa_private_pem, rsa_public_pem): assert 256 == len(signature) == rsa_private_pem.output_size assert plaintext == rsa_private_pem.verify(signature) + @pytest.mark.skipif(not _lib.PEM_TO_DER_ENABLED, reason="PEM to DER not enabled") + @needs_rng + @needs_sign_verify def test_rsa_sign_verify_pem_rng(rsa_private_pem_rng, rsa_public_pem_rng): plaintext = t2b("Everyone gets Friday off.") @@ -533,6 +603,9 @@ def test_rsa_sign_verify_pem_rng(rsa_private_pem_rng, rsa_public_pem_rng): assert 256 == len(signature) == rsa_private_pem_rng.output_size assert plaintext == rsa_private_pem_rng.verify(signature) + @pytest.mark.skipif(not _lib.PKCS8_ENABLED, reason="PKCS#8 not enabled") + @needs_rng + @needs_sign_verify def test_rsa_pkcs8_sign_verify(rsa_private_pkcs8, rsa_public): plaintext = t2b("Everyone gets Friday off.") @@ -551,6 +624,13 @@ def test_rsa_pkcs8_sign_verify(rsa_private_pkcs8, rsa_public): if _lib.ECC_ENABLED: + needs_ecc_import = pytest.mark.skipif(not _lib.ECC_KEY_IMPORT_ENABLED, reason="ECC key import not enabled") + needs_ecc_export = pytest.mark.skipif(not _lib.ECC_KEY_EXPORT_ENABLED, reason="ECC key export not enabled") + needs_ecc_sign_verify = pytest.mark.skipif(not (_lib.ECC_SIGN_ENABLED and _lib.ECC_VERIFY_ENABLED), + reason="ECC signing or verification not enabled") + needs_ecc_verify = pytest.mark.skipif(not _lib.ECC_VERIFY_ENABLED, reason="ECC verification not enabled") + needs_ecc_dhe = pytest.mark.skipif(not _lib.ECC_DHE_ENABLED, reason="ECDH not enabled") + @pytest.fixture def ecc_private(vectors): return EccPrivate(vectors[EccPrivate].key) @@ -561,6 +641,7 @@ def ecc_public(vectors): return EccPublic(vectors[EccPublic].key) + @needs_ecc_import def test_new_ecc_raises(vectors): with pytest.raises(WolfCryptError): EccPrivate(vectors[EccPrivate].key[:-1]) # invalid key length @@ -575,6 +656,8 @@ def test_new_ecc_raises(vectors): EccPrivate.make_key(1024) + @needs_ecc_import + @needs_ecc_export def test_key_encoding(vectors): priv = EccPrivate() pub = EccPublic() @@ -613,6 +696,55 @@ def test_key_encoding(vectors): assert qy[0:32] == vectors[EccPublic].raw_key[32:64] + @needs_ecc_import + @needs_ecc_export + def test_ecc_encode_key_buffer_not_from_field_size(vectors, monkeypatch): + """ + F-10070: the private key DER is larger than four times the field + size on small curves, so the output buffer must not be sized from it. + """ + monkeypatch.setattr(EccPrivate, "size", property(lambda self: 14)) + priv = EccPrivate(vectors[EccPrivate].key) + assert priv.encode_key() == vectors[EccPrivate].key + + + @pytest.mark.parametrize("curve_id", [ECC_SECP112R1, ECC_SECP128R1, ECC_SECP160R1, ECC_SECP192R1]) + @needs_ecc_import + @needs_ecc_export + def test_ecc_encode_key_small_curves(curve_id): + """ + F-10070: private key DER encoding round-trips on small curves. + """ + size = _lib.wc_ecc_get_curve_size_from_id(curve_id) + if size <= 0: + pytest.skip("curve not enabled") + if _lib.FIPS_ENABLED and _lib.FIPS_VERSION >= 6 and size < 28: + pytest.skip("FIPS 140-3 does not generate keys under 224 bits") + key = EccPrivate.make_key(size) + assert key.size == size + der = key.encode_key() + assert EccPrivate(der).encode_key() == der + + + @needs_ecc_import + @needs_ecc_export + def test_ecc_encode_key_p192_vector(): + """ + F-10070: an imported P-192 private key re-encodes to the same DER. + FIPS 140-3 builds can import P-192 keys but not generate them. + """ + if _lib.wc_ecc_get_curve_size_from_id(ECC_SECP192R1) <= 0: + pytest.skip("curve not enabled") + der = h2b( + "305f02010104189611a7935930fa834b7de535a371d3461d1ff4a309d15cb8a0" + "0a06082a8648ce3d030101a13403320004a9afcfd908947032399677a3ae1d9d" + "5ec75906e27f7d3ddeec030107d246b3550a48208cbcb9b1f15978f938b652ff" + "0b" + ) + assert EccPrivate(der).encode_key() == der + + + @needs_ecc_import def test_ecc_decode_key_raw_rejects_wrong_length(vectors): """ wc_ecc_import_unsigned reads exactly curve_size bytes from each @@ -646,59 +778,229 @@ def test_ecc_decode_key_raw_rejects_wrong_length(vectors): raw_priv.decode_key_raw(qx_good, qy_good, d_good) + P256_PRIME = h2b( + "ffffffff00000001000000000000000000000000ffffffffffffffffffffffff") + + + @pytest.mark.parametrize("bad", ["off_curve", "x_not_below_p"]) + @needs_ecc_import + def test_ecc_decode_key_raw_rejects_invalid_point(vectors, bad): + """ + F-8277: wc_ecc_import_unsigned does not validate the point, so + decode_key_raw must reject points that are not on the curve. + """ + key = vectors[EccPublic].raw_key + qx, qy = key[0:32], key[32:64] + if bad == "off_curve": + qy = qy[:-1] + bytes([qy[-1] ^ 1]) + else: + qx = P256_PRIME + + with pytest.raises(WolfCryptApiError): + EccPublic().decode_key_raw(qx, qy) + + + @needs_ecc_import + def test_ecc_import_rejects_off_curve_point(vectors): + """ + F-8277: import_x963 and decode_key reject a point that is not on + the curve. + """ + key = vectors[EccPublic].raw_key + bad_qy = key[32:63] + bytes([key[63] ^ 1]) + with pytest.raises(WolfCryptApiError): + EccPublic().import_x963(b"\x04" + key[0:32] + bad_qy) + + der = vectors[EccPublic].key + with pytest.raises(WolfCryptApiError): + EccPublic(der[:-1] + bytes([der[-1] ^ 1])) + + + @needs_ecc_import + def test_ecc_private_import_rejects_off_curve_point(vectors): + """ + decode_key_raw and decode_key of EccPrivate reject a public point + that is not on the curve, as EccPublic does. + """ + key = vectors[EccPrivate].raw_key + qx, qy, d = key[0:32], key[32:64], key[64:96] + with pytest.raises(WolfCryptApiError): + EccPrivate().decode_key_raw(qx, qy[:-1] + bytes([qy[-1] ^ 1]), d) + der = vectors[EccPrivate].key + with pytest.raises(WolfCryptApiError): + EccPrivate(der[:-1] + bytes([der[-1] ^ 1])) + + + @needs_ecc_import + def test_ecc_private_key_without_public_key(vectors): + """The public key is optional in an ECPrivateKey.""" + der = vectors[EccPrivate].key + # Drop the trailing [1] publicKey field and fix the SEQUENCE length. + assert der[:2] == b"\x30\x77" and der[51] == 0xa1 + assert EccPrivate(b"\x30\x31" + der[2:51]).size == 32 + + + + @needs_ecc_import + @needs_ecc_export def test_x963(ecc_private, ecc_public): assert ecc_private.export_x963() == ecc_public.export_x963() + @needs_ecc_import + @needs_ecc_export + @needs_ecc_sign_verify def test_ecc_sign_verify(ecc_private, ecc_public): - plaintext = "Everyone gets Friday off." + digest = hashlib.sha256(b"Everyone gets Friday off.").digest() # normal usage, sign with private, verify with public - signature = ecc_private.sign(plaintext) + signature = ecc_private.sign(digest) assert len(signature) <= ecc_private.max_signature_size - assert ecc_public.verify(signature, plaintext) + assert ecc_public.verify(signature, digest) # invalid signature with pytest.raises(WolfCryptError): - ecc_public.verify(signature[:-1], plaintext) + ecc_public.verify(signature[:-1], digest) # private object holds both private and public info, so it can also verify # using the known public key. - assert ecc_private.verify(signature, plaintext) + assert ecc_private.verify(signature, digest) ecc_x963 = EccPublic() ecc_x963.import_x963(ecc_public.export_x963()) - assert ecc_x963.verify(signature, plaintext) + assert ecc_x963.verify(signature, digest) ecc_x963 = EccPublic() ecc_x963.import_x963(ecc_private.export_x963()) - assert ecc_x963.verify(signature, plaintext) + assert ecc_x963.verify(signature, digest) ecc_x963 = EccPublic() with pytest.raises(WolfCryptError): ecc_x963.import_x963(ecc_public.export_x963()[:-1]) if _lib.MPAPI_ENABLED: + @needs_ecc_import + @needs_ecc_sign_verify def test_ecc_sign_verify_raw(ecc_private, ecc_public): - plaintext = "Everyone gets Friday off." + digest = hashlib.sha256(b"Everyone gets Friday off.").digest() # normal usage, sign with private, verify with public - r,s = ecc_private.sign_raw(plaintext) + r,s = ecc_private.sign_raw(digest) assert len(r) + len(s) <= 2 * ecc_private.size - assert ecc_public.verify_raw(r, s, plaintext) + assert ecc_public.verify_raw(r, s, digest) # invalid signature - ret = ecc_public.verify_raw(r, s[:-1], plaintext) + ret = ecc_public.verify_raw(r, s[:-1], digest) assert not ret # private object holds both private and public info, so it can also verify # using the known public key. - assert ecc_private.verify_raw(r, s, plaintext) + assert ecc_private.verify_raw(r, s, digest) + # Made with `openssl dgst -sha256 -sign` and the vectors[EccPrivate] key. + ECC_OPENSSL_MESSAGE = b"Everyone gets Friday off. This message is longer than one digest." + ECC_OPENSSL_SHA256_SIGNATURE = h2b( + "3045022100b2314357b468577038b0eb8fc7e051e40eb729e6e3319d5bfd3b4cbc78be73cb" + "022076c30495ee4e21edf2ac2086d4fb426e413ee26f009f09c3daa312ba7065c35f") + + + @needs_ecc_import + @needs_ecc_verify + def test_ecc_verify_openssl_signature(ecc_public): + """ + F-8279: verify() takes the digest of the message, as signed by + `openssl dgst -sha256 -sign`. + """ + digest = hashlib.sha256(ECC_OPENSSL_MESSAGE).digest() + other = hashlib.sha256(ECC_OPENSSL_MESSAGE[:-1]).digest() + assert ecc_public.verify(ECC_OPENSSL_SHA256_SIGNATURE, digest) + assert not ecc_public.verify(ECC_OPENSSL_SHA256_SIGNATURE, other) + + + @pytest.mark.parametrize("length", [0, 16, 25, 33, 65, 100]) + @needs_ecc_import + @needs_ecc_sign_verify + def test_ecc_sign_rejects_non_digest_length(ecc_private, ecc_public, length): + """ + F-8279: sign() and verify() take a digest, so an input that is not + the size of a SHA-1 or SHA-2 digest raises ValueError. + """ + signature = ecc_private.sign(hashlib.sha256(b"message").digest()) + with pytest.raises(ValueError, match="digest"): + ecc_private.sign(b"\x01" * length) + with pytest.raises(ValueError, match="digest"): + ecc_public.verify(signature, b"\x01" * length) + + + @pytest.mark.parametrize("hash_name", ["sha1", "sha224", "sha256", "sha384", "sha512"]) + @needs_ecc_import + @needs_ecc_sign_verify + def test_ecc_sign_digest_sizes(ecc_private, ecc_public, hash_name): + """ + F-8279: SHA-1 and SHA-2 digest sizes are accepted. + """ + digest = hashlib.new(hash_name, b"message").digest() + if not _lib.WC_MIN_DIGEST_SIZE <= len(digest) <= _lib.WC_MAX_DIGEST_SIZE: + pytest.skip("digest size not accepted by this wolfSSL build") + assert ecc_public.verify(ecc_private.sign(digest), digest) + + + if _lib.MPAPI_ENABLED: + # r and s of ECC_OPENSSL_SHA256_SIGNATURE. + ECC_OPENSSL_SHA256_R = h2b("b2314357b468577038b0eb8fc7e051e40eb729e6e3319d5bfd3b4cbc78be73cb") + ECC_OPENSSL_SHA256_S = h2b("76c30495ee4e21edf2ac2086d4fb426e413ee26f009f09c3daa312ba7065c35f") + + + @needs_ecc_import + @needs_ecc_verify + def test_ecc_verify_raw_openssl_signature(ecc_public): + """ + F-8280: verify_raw() takes the digest of the message, as signed by + `openssl dgst -sha256 -sign`. + """ + r, s = ECC_OPENSSL_SHA256_R, ECC_OPENSSL_SHA256_S + digest = hashlib.sha256(ECC_OPENSSL_MESSAGE).digest() + other = hashlib.sha256(ECC_OPENSSL_MESSAGE[:-1]).digest() + assert ecc_public.verify_raw(r, s, digest) + assert not ecc_public.verify_raw(r, s, other) + + + @pytest.mark.parametrize("length", [0, 16, 25, 33, 65, 100]) + @needs_ecc_import + @needs_ecc_sign_verify + def test_ecc_sign_raw_rejects_non_digest_length(ecc_private, ecc_public, length): + """ + F-8280: sign_raw() and verify_raw() take a digest, so an input that + is not the size of a SHA-1 or SHA-2 digest raises ValueError. + """ + r, s = ecc_private.sign_raw(hashlib.sha256(b"message").digest()) + with pytest.raises(ValueError, match="digest"): + ecc_private.sign_raw(b"\x01" * length) + with pytest.raises(ValueError, match="digest"): + ecc_public.verify_raw(r, s, b"\x01" * length) + + + @pytest.mark.parametrize("hash_name", ["sha1", "sha224", "sha256", "sha384", "sha512"]) + @needs_ecc_import + @needs_ecc_sign_verify + def test_ecc_sign_raw_digest_sizes(ecc_private, ecc_public, hash_name): + """ + F-8280: SHA-1 and SHA-2 digest sizes are accepted. + """ + digest = hashlib.new(hash_name, b"message").digest() + if not _lib.WC_MIN_DIGEST_SIZE <= len(digest) <= _lib.WC_MAX_DIGEST_SIZE: + pytest.skip("digest size not accepted by this wolfSSL build") + r, s = ecc_private.sign_raw(digest) + assert ecc_public.verify_raw(r, s, digest) + + + @needs_ecc_import + @needs_ecc_export + @needs_ecc_dhe def test_ecc_make_shared_secret(): a = EccPrivate.make_key(32, rng=Random()) a_pub = EccPublic() @@ -713,6 +1015,9 @@ def test_ecc_make_shared_secret(): == a.shared_secret(b_pub) \ == b.shared_secret(a_pub) + @needs_ecc_import + @needs_ecc_export + @needs_ecc_dhe def test_ecc_make_key_no_rng(): key = EccPrivate.make_key(32) pub_key = EccPublic() @@ -721,6 +1026,15 @@ def test_ecc_make_key_no_rng(): assert key.shared_secret(pub_key) if _lib.ED25519_ENABLED: + needs_ed25519_make_key = pytest.mark.skipif(not _lib.ED25519_MAKE_KEY_ENABLED, + reason="Ed25519 key generation not enabled") + needs_ed25519_import = pytest.mark.skipif(not _lib.ED25519_KEY_IMPORT_ENABLED, + reason="Ed25519 key import not enabled") + needs_ed25519_export = pytest.mark.skipif(not _lib.ED25519_KEY_EXPORT_ENABLED, + reason="Ed25519 key export not enabled") + needs_ed25519_sign_verify = pytest.mark.skipif(not (_lib.ED25519_SIGN_ENABLED and _lib.ED25519_VERIFY_ENABLED), + reason="Ed25519 signing or verification not enabled") + @pytest.fixture def ed25519_private(vectors): return Ed25519Private(vectors[Ed25519Private].key, vectors[Ed25519Public].key) @@ -731,6 +1045,8 @@ def ed25519_public(vectors): return Ed25519Public(vectors[Ed25519Public].key) + @needs_ed25519_import + @needs_ed25519_make_key def test_new_ed25519_raises(vectors): with pytest.raises(WolfCryptError): Ed25519Private(vectors[Ed25519Private].key[:-1]) # invalid key length @@ -738,10 +1054,14 @@ def test_new_ed25519_raises(vectors): with pytest.raises(WolfCryptError): Ed25519Public(vectors[Ed25519Public].key[:-1]) # invalid key length - with pytest.raises(WolfCryptError): # invalid key size - Ed25519Private.make_key(1024) + if _lib.RNG_ENABLED: + with pytest.raises(WolfCryptError): # invalid key size + Ed25519Private.make_key(1024) + @needs_ed25519_import + @needs_ed25519_export + @needs_ed25519_make_key def test_ed25519_key_encoding(vectors): priv = Ed25519Private() pub = Ed25519Public() @@ -754,6 +1074,8 @@ def test_ed25519_key_encoding(vectors): assert pub.encode_key() == vectors[Ed25519Public].key + @needs_ed25519_import + @needs_ed25519_sign_verify def test_ed25519_sign_verify(ed25519_private, ed25519_public): plaintext = "Everyone gets Friday off." @@ -772,6 +1094,13 @@ def test_ed25519_sign_verify(ed25519_private, ed25519_public): assert ed25519_private.verify(signature, plaintext) if _lib.ED448_ENABLED: + needs_ed448_import = pytest.mark.skipif(not _lib.ED448_KEY_IMPORT_ENABLED, + reason="Ed448 key import not enabled") + needs_ed448_export = pytest.mark.skipif(not _lib.ED448_KEY_EXPORT_ENABLED, + reason="Ed448 key export not enabled") + needs_ed448_sign_verify = pytest.mark.skipif(not (_lib.ED448_SIGN_ENABLED and _lib.ED448_VERIFY_ENABLED), + reason="Ed448 signing or verification not enabled") + @pytest.fixture def ed448_private(vectors): return Ed448Private(vectors[Ed448Private].key, vectors[Ed448Public].key) @@ -782,6 +1111,7 @@ def ed448_public(vectors): return Ed448Public(vectors[Ed448Public].key) + @needs_ed448_import def test_new_ed448_raises(vectors): with pytest.raises(WolfCryptError): Ed448Private(vectors[Ed448Private].key[:-1]) # invalid key length @@ -789,10 +1119,13 @@ def test_new_ed448_raises(vectors): with pytest.raises(WolfCryptError): Ed448Public(vectors[Ed448Public].key[:-1]) # invalid key length - with pytest.raises(WolfCryptError): # invalid key size - Ed448Private.make_key(1024) + if _lib.RNG_ENABLED: + with pytest.raises(WolfCryptError): # invalid key size + Ed448Private.make_key(1024) + @needs_ed448_import + @needs_ed448_export def test_ed448_key_encoding(vectors): priv = Ed448Private() pub = Ed448Public() @@ -805,6 +1138,8 @@ def test_ed448_key_encoding(vectors): assert pub.encode_key() == vectors[Ed448Public].key + @needs_ed448_import + @needs_ed448_sign_verify def test_ed448_sign_verify(ed448_private, ed448_public): plaintext = "Everyone gets Friday off." @@ -1011,6 +1346,36 @@ def test_des3_rejects_mode_ecb(): Des3.new(key, MODE_ECB, iv) +if _lib.AES_ENABLED: + def test_aes_ctr_rejected_when_not_compiled_in(monkeypatch): + """F-11250: MODE_CTR needs AES-CTR support in the linked wolfSSL.""" + monkeypatch.setattr(_lib, "AES_CTR_ENABLED", 0) + with pytest.raises(NotImplementedError, match="AES-CTR"): + Aes.new(b"0" * 16, MODE_CTR, b"0" * 16) + if _lib.AES_CBC_ENABLED: + assert Aes.new(b"0" * 16, MODE_CBC, b"0" * 16).encrypt(b"0" * 16) + + def test_aes_cbc_rejected_when_not_compiled_in(monkeypatch): + """F-12224: MODE_CBC needs AES-CBC support in the linked wolfSSL.""" + monkeypatch.setattr(_lib, "AES_CBC_ENABLED", 0) + with pytest.raises(NotImplementedError, match="AES-CBC"): + Aes.new(b"0" * 16, MODE_CBC, b"0" * 16) + + def test_aes_cbc_decrypt_rejected_when_not_compiled_in(monkeypatch, vectors): + """F-12224: CBC decryption needs AES decryption in the linked wolfSSL.""" + monkeypatch.setattr(_lib, "AES_DECRYPT_ENABLED", 0) + vector = vectors[Aes] + if _lib.AES_CBC_ENABLED: + cipher_obj = Aes.new(vector.key, MODE_CBC, vector.iv) + assert cipher_obj.encrypt(vector.plaintext) == vector.ciphertext + with pytest.raises(NotImplementedError, match="AES-CBC decryption"): + cipher_obj.decrypt(vector.ciphertext) + # CTR decryption only uses the encryption key schedule. + if _lib.AES_CTR_ENABLED: + cipher_obj = Aes.new(vector.key, MODE_CTR, vector.iv) + assert cipher_obj.decrypt(vector.ciphertext_ctr) == vector.plaintext + + if _lib.CHACHA_ENABLED: def test_chacha_non_block_aligned(): key = b"\x00" * 32 @@ -1107,12 +1472,298 @@ def test_chacha_set_iv_resets_both_directions(): if _lib.RSA_ENABLED: + @needs_rng + @pytest.mark.skipif(not (_lib.RSA_ENCRYPT_ENABLED and _lib.RSA_OAEP_ENABLED), reason="RSA OAEP encryption not enabled") def test_encrypt_oaep_requires_hash_type(vectors): rsa = RsaPublic(vectors[RsaPublic].key) with pytest.raises(WolfCryptError, match="Hash type not set"): rsa.encrypt_oaep(b"plaintext") + @needs_rng + @pytest.mark.skipif(not (_lib.RSA_PRIVATE_ENABLED and _lib.RSA_OAEP_ENABLED), reason="RSA OAEP decryption not enabled") def test_decrypt_oaep_requires_hash_type(vectors): rsa = RsaPrivate(vectors[RsaPrivate].key) with pytest.raises(WolfCryptError, match="Hash type not set"): rsa.decrypt_oaep(b"\x00" * rsa.output_size) + + def test_rsa_from_pem_defined_only_when_enabled(monkeypatch): + """F-12233: from_pem needs wc_PemToDer in the linked wolfSSL.""" + for cls in (RsaPublic, RsaPrivate): + assert hasattr(cls, "from_pem") == bool(_lib.PEM_TO_DER_ENABLED), cls + + # Load a fresh copy of the module as if wc_PemToDer were not compiled in. + monkeypatch.setattr(_lib, "PEM_TO_DER_ENABLED", 0) + module = load_fresh_ciphers() + for cls in (module.RsaPublic, module.RsaPrivate): + assert not hasattr(cls, "from_pem"), cls + + @needs_rng + def test_rsa_private_without_pkcs8_offset(monkeypatch, vectors): + """F-12234: RsaPrivate must not need wc_GetPkcs8TraditionalOffset without PKCS#8.""" + class LibWithoutPkcs8: + PKCS8_ENABLED = 0 + + def __getattr__(self, name): + if name == "wc_GetPkcs8TraditionalOffset": + raise AttributeError(name) + return getattr(_lib, name) + + monkeypatch.setattr(ciphers, "_lib", LibWithoutPkcs8()) + with pytest.raises(WolfCryptApiError): + RsaPrivate(vectors[RsaPrivate].key[:-1]) + assert RsaPrivate(vectors[RsaPrivate].key).output_size == 128 + if _lib.PKCS8_ENABLED: + # wc_RsaPrivateKeyDecode skips a PKCS#8 header by itself. + assert RsaPrivate(vectors[RsaPrivate].pkcs8_key).output_size == 128 + + # Method -> flags of the wolfSSL operations it needs. + RSA_GATED_METHODS = { + ("RsaPublic", "encrypt"): ("RSA_ENCRYPT_ENABLED",), + ("RsaPublic", "encrypt_oaep"): ("RSA_ENCRYPT_ENABLED", "RSA_OAEP_ENABLED"), + ("RsaPrivate", "decrypt"): ("RSA_PRIVATE_ENABLED",), + ("RsaPrivate", "decrypt_oaep"): ("RSA_PRIVATE_ENABLED", "RSA_OAEP_ENABLED"), + ("RsaPrivate", "make_key"): ("KEYGEN_ENABLED", "RSA_PRIVATE_ENABLED"), + ("RsaPrivate", "sign_pss"): ("RSA_PSS_ENABLED", "RSA_SIGN_ENABLED"), + } + + def test_rsa_methods_defined_only_when_enabled(monkeypatch): + """F-12227: each RSA method needs its wolfSSL operation to be compiled in.""" + for (cls, name), flags in RSA_GATED_METHODS.items(): + enabled = all(getattr(_lib, flag) for flag in flags) + assert hasattr(getattr(ciphers, cls), name) == enabled, (cls, name) + + # Load fresh copies of the module as if one operation were not compiled in. + for disabled in ("RSA_ENCRYPT_ENABLED", "RSA_PRIVATE_ENABLED", "RSA_SIGN_ENABLED", "RSA_OAEP_ENABLED"): + with monkeypatch.context() as m: + m.setattr(_lib, disabled, 0) + module = load_fresh_ciphers() + for (cls, name), flags in RSA_GATED_METHODS.items(): + if disabled in flags: + assert not hasattr(getattr(module, cls), name), (disabled, cls, name) + # sign() and verify() implement the RSA protocols, so they stay. + assert hasattr(module.RsaPrivate, "sign"), disabled + assert hasattr(module.RsaPublic, "verify"), disabled + + @needs_rng + def test_rsa_sign_rejected_when_not_compiled_in(monkeypatch, vectors): + """F-12227: sign() needs RSA signing in the linked wolfSSL.""" + rsa = RsaPrivate(vectors[RsaPrivate].key) + monkeypatch.setattr(_lib, "RSA_SIGN_ENABLED", 0) + with pytest.raises(NotImplementedError, match="RSA signing is not supported"): + rsa.sign(b"Everyone gets Friday off.") + + @needs_rng + @pytest.mark.skipif(not _lib.RSA_SIGN_ENABLED, reason="RSA signing not enabled") + def test_rsa_verify_rejected_when_not_compiled_in(monkeypatch, vectors): + """F-12227: verify() needs wc_RsaSSL_Verify in the linked wolfSSL.""" + signature = RsaPrivate(vectors[RsaPrivate].key).sign(b"Everyone gets Friday off.") + rsa = RsaPublic(vectors[RsaPublic].key) + monkeypatch.setattr(_lib, "RSA_VERIFY_ENABLED", 0) + with pytest.raises(NotImplementedError, match="RSA verification is not supported"): + rsa.verify(signature) + +if _lib.ECC_ENABLED: + # Method -> flags of the wolfSSL operations it needs. + ECC_GATED_METHODS = { + ("EccPublic", "decode_key"): ("ECC_KEY_IMPORT_ENABLED",), + ("EccPublic", "decode_key_raw"): ("ECC_KEY_IMPORT_ENABLED",), + ("EccPublic", "import_x963"): ("ECC_KEY_IMPORT_ENABLED",), + ("EccPublic", "encode_key"): ("ECC_KEY_EXPORT_ENABLED",), + ("EccPublic", "encode_key_raw"): ("ECC_KEY_EXPORT_ENABLED",), + ("EccPublic", "export_x963"): ("ECC_KEY_EXPORT_ENABLED",), + ("EccPublic", "verify"): ("ECC_VERIFY_ENABLED",), + ("EccPublic", "verify_raw"): ("ECC_VERIFY_ENABLED", "MPAPI_ENABLED"), + ("EccPrivate", "decode_key"): ("ECC_KEY_IMPORT_ENABLED",), + ("EccPrivate", "decode_key_raw"): ("ECC_KEY_IMPORT_ENABLED",), + ("EccPrivate", "encode_key"): ("ECC_KEY_EXPORT_ENABLED",), + ("EccPrivate", "encode_key_raw"): ("ECC_KEY_EXPORT_ENABLED",), + ("EccPrivate", "shared_secret"): ("ECC_DHE_ENABLED",), + ("EccPrivate", "sign"): ("ECC_SIGN_ENABLED",), + ("EccPrivate", "sign_raw"): ("ECC_SIGN_ENABLED", "MPAPI_ENABLED"), + } + + def test_ecc_methods_defined_only_when_enabled(monkeypatch): + """F-12228: each ECC method needs its wolfSSL operation to be compiled in.""" + for (cls, name), flags in ECC_GATED_METHODS.items(): + enabled = all(getattr(_lib, flag) for flag in flags) + assert hasattr(getattr(ciphers, cls), name) == enabled, (cls, name) + + # Load fresh copies of the module as if one operation were not compiled in. + for disabled in ("ECC_KEY_IMPORT_ENABLED", "ECC_KEY_EXPORT_ENABLED", "ECC_DHE_ENABLED", + "ECC_SIGN_ENABLED", "ECC_VERIFY_ENABLED"): + with monkeypatch.context() as m: + m.setattr(_lib, disabled, 0) + module = load_fresh_ciphers() + for (cls, name), flags in ECC_GATED_METHODS.items(): + enabled = all(getattr(_lib, flag) for flag in flags) + assert hasattr(getattr(module, cls), name) == enabled, (disabled, cls, name) + assert hasattr(module.EccPrivate, "make_key"), disabled + + def test_ecc_key_rejected_without_key_import(monkeypatch, vectors): + """F-12228: loading a key needs ECC key import in the linked wolfSSL.""" + monkeypatch.setattr(_lib, "ECC_KEY_IMPORT_ENABLED", 0) + for cls in (EccPublic, EccPrivate): + with pytest.raises(NotImplementedError, match="ECC key import is not supported"): + cls(vectors[cls].key) + assert cls().size == 0, cls + +if _lib.ED25519_ENABLED: + # Method -> flag of the wolfSSL operation it needs. + ED25519_GATED_METHODS = { + ("Ed25519Public", "decode_key"): "ED25519_KEY_IMPORT_ENABLED", + ("Ed25519Public", "encode_key"): "ED25519_KEY_EXPORT_ENABLED", + ("Ed25519Public", "verify"): "ED25519_VERIFY_ENABLED", + ("Ed25519Private", "make_key"): "ED25519_MAKE_KEY_ENABLED", + ("Ed25519Private", "decode_key"): "ED25519_KEY_IMPORT_ENABLED", + ("Ed25519Private", "encode_key"): "ED25519_KEY_EXPORT_ENABLED", + ("Ed25519Private", "sign"): "ED25519_SIGN_ENABLED", + } + + def test_ed25519_methods_defined_only_when_enabled(monkeypatch): + """F-12229: each Ed25519 method needs its wolfSSL operation to be compiled in.""" + for (cls, name), flag in ED25519_GATED_METHODS.items(): + assert hasattr(getattr(ciphers, cls), name) == bool(getattr(_lib, flag)), (cls, name) + + # Load fresh copies of the module as if one operation were not compiled in. + for disabled in set(ED25519_GATED_METHODS.values()): + with monkeypatch.context() as m: + m.setattr(_lib, disabled, 0) + module = load_fresh_ciphers() + for (cls, name), flag in ED25519_GATED_METHODS.items(): + assert hasattr(getattr(module, cls), name) == bool(getattr(_lib, flag)), (disabled, cls, name) + + def test_ed25519_key_rejected_without_key_import(monkeypatch, vectors): + """F-12229: loading a key needs Ed25519 key import in the linked wolfSSL.""" + monkeypatch.setattr(_lib, "ED25519_KEY_IMPORT_ENABLED", 0) + for cls in (Ed25519Public, Ed25519Private): + with pytest.raises(NotImplementedError, match="Ed25519 key import is not supported"): + cls(vectors[cls].key) + cls() + with pytest.raises(NotImplementedError, match="Ed25519 key import is not supported"): + Ed25519Private(vectors[Ed25519Private].key, vectors[Ed25519Public].key) + + @needs_ed25519_import + def test_ed25519_private_only_key_needs_make_key(monkeypatch, vectors): + """F-12229: deriving the public key needs wc_ed25519_make_public.""" + monkeypatch.setattr(_lib, "ED25519_MAKE_KEY_ENABLED", 0) + with pytest.raises(NotImplementedError, match="Deriving the Ed25519 public key is not supported"): + Ed25519Private(vectors[Ed25519Private].key) + Ed25519Private(vectors[Ed25519Private].key, vectors[Ed25519Public].key) + +if _lib.ED448_ENABLED: + # Method -> flag of the wolfSSL operation it needs. + ED448_GATED_METHODS = { + ("Ed448Public", "decode_key"): "ED448_KEY_IMPORT_ENABLED", + ("Ed448Public", "encode_key"): "ED448_KEY_EXPORT_ENABLED", + ("Ed448Public", "verify"): "ED448_VERIFY_ENABLED", + ("Ed448Private", "decode_key"): "ED448_KEY_IMPORT_ENABLED", + ("Ed448Private", "encode_key"): "ED448_KEY_EXPORT_ENABLED", + ("Ed448Private", "sign"): "ED448_SIGN_ENABLED", + } + + def test_ed448_methods_defined_only_when_enabled(monkeypatch): + """F-12230: each Ed448 method needs its wolfSSL operation to be compiled in.""" + for (cls, name), flag in ED448_GATED_METHODS.items(): + assert hasattr(getattr(ciphers, cls), name) == bool(getattr(_lib, flag)), (cls, name) + assert hasattr(ciphers.Ed448Private, "make_key") + + # Load fresh copies of the module as if one operation were not compiled in. + for disabled in set(ED448_GATED_METHODS.values()): + with monkeypatch.context() as m: + m.setattr(_lib, disabled, 0) + module = load_fresh_ciphers() + for (cls, name), flag in ED448_GATED_METHODS.items(): + assert hasattr(getattr(module, cls), name) == bool(getattr(_lib, flag)), (disabled, cls, name) + assert hasattr(module.Ed448Private, "make_key"), disabled + + def test_ed448_key_rejected_without_key_import(monkeypatch, vectors): + """F-12230: loading a key needs Ed448 key import in the linked wolfSSL.""" + monkeypatch.setattr(_lib, "ED448_KEY_IMPORT_ENABLED", 0) + for cls in (Ed448Public, Ed448Private): + with pytest.raises(NotImplementedError, match="Ed448 key import is not supported"): + cls(vectors[cls].key) + cls() + with pytest.raises(NotImplementedError, match="Ed448 key import is not supported"): + Ed448Private(vectors[Ed448Private].key, vectors[Ed448Public].key) + +if _lib.ML_KEM_ENABLED: + # Method -> flag of the wolfSSL operation it needs. + ML_KEM_GATED_METHODS = { + ("MlKemPublic", "encapsulate"): "ML_KEM_ENCAPSULATE_ENABLED", + ("MlKemPublic", "encapsulate_with_random"): "ML_KEM_ENCAPSULATE_ENABLED", + ("MlKemPrivate", "make_key"): "ML_KEM_MAKE_KEY_ENABLED", + ("MlKemPrivate", "make_key_with_random"): "ML_KEM_MAKE_KEY_ENABLED", + ("MlKemPrivate", "decapsulate"): "ML_KEM_DECAPSULATE_ENABLED", + } + ML_KEM_COMMON_METHODS = ( + ("MlKemPublic", "decode_key"), + ("MlKemPublic", "encode_key"), + ("MlKemPrivate", "decode_key"), + ("MlKemPrivate", "encode_priv_key"), + ("MlKemPrivate", "encode_pub_key"), + ) + + def test_ml_kem_methods_defined_only_when_enabled(monkeypatch): + """F-12231: each ML-KEM method needs its wolfSSL operation to be compiled in.""" + for (cls, name), flag in ML_KEM_GATED_METHODS.items(): + assert hasattr(getattr(ciphers, cls), name) == bool(getattr(_lib, flag)), (cls, name) + + # Load fresh copies of the module as if one operation were not compiled in. + for disabled in set(ML_KEM_GATED_METHODS.values()): + with monkeypatch.context() as m: + m.setattr(_lib, disabled, 0) + module = load_fresh_ciphers() + for (cls, name), flag in ML_KEM_GATED_METHODS.items(): + assert hasattr(getattr(module, cls), name) == bool(getattr(_lib, flag)), (disabled, cls, name) + for cls, name in ML_KEM_COMMON_METHODS: + assert hasattr(getattr(module, cls), name), (disabled, cls, name) + +if _lib.ML_DSA_ENABLED: + # Method -> whether the wolfSSL operations it needs are compiled in. + ML_DSA_GATED_METHODS = { + ("MlDsaPublic", "key_size"): lambda: _lib.ML_DSA_PUBLIC_KEY_ENABLED, + ("MlDsaPublic", "decode_key"): lambda: _lib.ML_DSA_PUBLIC_KEY_ENABLED, + ("MlDsaPublic", "encode_key"): lambda: _lib.ML_DSA_PUBLIC_KEY_ENABLED, + ("MlDsaPublic", "sig_size"): lambda: _lib.ML_DSA_SIGN_ENABLED or _lib.ML_DSA_VERIFY_ENABLED, + ("MlDsaPublic", "verify"): lambda: _lib.ML_DSA_VERIFY_ENABLED, + ("MlDsaPrivate", "make_key"): lambda: _lib.ML_DSA_MAKE_KEY_ENABLED, + ("MlDsaPrivate", "make_key_from_seed"): lambda: _lib.ML_DSA_MAKE_KEY_ENABLED, + ("MlDsaPrivate", "pub_key_size"): lambda: _lib.ML_DSA_PUBLIC_KEY_ENABLED, + ("MlDsaPrivate", "encode_pub_key"): lambda: _lib.ML_DSA_PUBLIC_KEY_ENABLED, + ("MlDsaPrivate", "priv_key_size"): lambda: _lib.ML_DSA_PRIVATE_KEY_ENABLED and _lib.ML_DSA_PUBLIC_KEY_ENABLED, + ("MlDsaPrivate", "encode_priv_key"): lambda: _lib.ML_DSA_PRIVATE_KEY_ENABLED and _lib.ML_DSA_PUBLIC_KEY_ENABLED, + ("MlDsaPrivate", "decode_key"): lambda: _lib.ML_DSA_PRIVATE_KEY_ENABLED, + ("MlDsaPrivate", "sig_size"): lambda: _lib.ML_DSA_SIGN_ENABLED or _lib.ML_DSA_VERIFY_ENABLED, + ("MlDsaPrivate", "sign"): lambda: _lib.ML_DSA_SIGN_ENABLED, + ("MlDsaPrivate", "sign_with_seed"): lambda: _lib.ML_DSA_SIGN_ENABLED, + ("MlDsaPrivate", "verify"): lambda: _lib.ML_DSA_VERIFY_ENABLED, + } + + @pytest.mark.parametrize("disabled", [ + (), + ("ML_DSA_MAKE_KEY_ENABLED",), + ("ML_DSA_SIGN_ENABLED",), + ("ML_DSA_VERIFY_ENABLED",), + ("ML_DSA_PUBLIC_KEY_ENABLED",), + ("ML_DSA_PRIVATE_KEY_ENABLED",), + ("ML_DSA_SIGN_ENABLED", "ML_DSA_VERIFY_ENABLED"), + ("ML_DSA_MAKE_KEY_ENABLED", "ML_DSA_SIGN_ENABLED", "ML_DSA_PRIVATE_KEY_ENABLED"), + ]) + def test_ml_dsa_methods_defined_only_when_enabled(monkeypatch, disabled): + """F-10071: each ML-DSA method needs its wolfSSL operations to be compiled in.""" + for flag in disabled: + monkeypatch.setattr(_lib, flag, 0) + # Load a fresh copy of the module as if the operations were not compiled in. + module = load_fresh_ciphers() + for (cls, name), enabled in ML_DSA_GATED_METHODS.items(): + assert hasattr(getattr(module, cls), name) == bool(enabled()), (disabled, cls, name) + + def test_ml_dsa_private_decode_needs_public_key_import(monkeypatch): + """F-10071: importing the public key part needs ML-DSA public key support.""" + if not _lib.ML_DSA_PRIVATE_KEY_ENABLED: + pytest.skip("ML-DSA private key support not enabled") + monkeypatch.setattr(_lib, "ML_DSA_PUBLIC_KEY_ENABLED", 0) + module = load_fresh_ciphers() + key = module.MlDsaPrivate(module.MlDsaType.ML_DSA_44) + with pytest.raises(NotImplementedError, match="ML-DSA public key import is not supported"): + key.decode_key(b"\x00" * 16, b"\x00" * 16) diff --git a/tests/test_delete_descriptor_binding.py b/tests/test_delete_descriptor_binding.py index 226c6d3..d9b4db3 100644 --- a/tests/test_delete_descriptor_binding.py +++ b/tests/test_delete_descriptor_binding.py @@ -51,8 +51,9 @@ def __del__(self): def _static_attrs(): """Yield (cls, attr_name) pairs that must be staticmethod-wrapped.""" - from wolfcrypt.random import Random - yield Random, "_delete" + if _lib.RNG_ENABLED: + from wolfcrypt.random import Random + yield Random, "_delete" if _lib.SHA_ENABLED: from wolfcrypt.hashes import Sha # ty: ignore[possibly-missing-import] @@ -151,6 +152,7 @@ def run(self): ) +@pytest.mark.skipif(not _lib.RNG_ENABLED, reason="RNG not enabled") def test_random_delete_receives_only_native_object(): """End-to-end behavioral check on the real ``Random`` class. diff --git a/tests/test_hashes.py b/tests/test_hashes.py index 12b282a..eea3cee 100644 --- a/tests/test_hashes.py +++ b/tests/test_hashes.py @@ -84,7 +84,7 @@ def vectors(): "e8fff55e644ee8a106aae19c07f91b3f" "2a2a6d40dfa7302c0fa6a1a9a5bfa03f") ) - if _lib.SHA3_ENABLED: + if _lib.SHA3_ENABLED and _lib.SHA3_384_ENABLED: vectorArray[Sha3]=TestVector( digest=t2b("6170dedf06f83c3305ec18b7558384a5" "a62d86e42c143d416aaec32f971986c1" @@ -124,7 +124,7 @@ def vectors(): hash_params.append(Sha384) # ty: ignore[possibly-unresolved-reference] if _lib.SHA512_ENABLED: hash_params.append(Sha512) # ty: ignore[possibly-unresolved-reference] -if _lib.SHA3_ENABLED: +if _lib.SHA3_ENABLED and _lib.SHA3_384_ENABLED: hash_params.append(Sha3) # ty: ignore[possibly-unresolved-reference] hmac_params = [] @@ -218,3 +218,22 @@ def test_hash_copy_destroy_lifecycle(hash_cls, vectors): assert c.hexdigest() == digest del c gc.collect() + + +if _lib.SHA3_ENABLED: + SHA3_FLAGS = { + 28: "SHA3_224_ENABLED", + 32: "SHA3_256_ENABLED", + 48: "SHA3_384_ENABLED", + 64: "SHA3_512_ENABLED", + } + + @pytest.mark.parametrize("size", sorted(SHA3_FLAGS)) + def test_sha3_size_not_compiled_in(monkeypatch, size): + """F-12226: a SHA-3 size missing from the linked wolfSSL raises NotImplementedError.""" + monkeypatch.setattr(_lib, SHA3_FLAGS[size], 0) + with pytest.raises(NotImplementedError, match="SHA3"): + Sha3("wolfcrypt", size) # ty: ignore[possibly-unresolved-reference] + for other, flag in SHA3_FLAGS.items(): + if other != size and getattr(_lib, flag): + assert len(Sha3("wolfcrypt", other).digest()) == other # ty: ignore[possibly-unresolved-reference] diff --git a/tests/test_hkdf.py b/tests/test_hkdf.py index a19b483..3f14288 100644 --- a/tests/test_hkdf.py +++ b/tests/test_hkdf.py @@ -24,8 +24,10 @@ import pytest from wolfcrypt._ffi import lib as _lib -from wolfcrypt.hkdf import HKDF, HKDF_Extract, HKDF_Expand -from wolfcrypt.hashes import HmacSha, HmacSha256 + +if _lib.HKDF_ENABLED: + from wolfcrypt.hkdf import HKDF, HKDF_Extract, HKDF_Expand + from wolfcrypt.hashes import HmacSha, HmacSha256 # Skip the whole module if required features are not available. pytestmark = pytest.mark.skipif( diff --git a/tests/test_mldsa.py b/tests/test_mldsa.py index 5c4b0db..6163138 100644 --- a/tests/test_mldsa.py +++ b/tests/test_mldsa.py @@ -31,6 +31,8 @@ @pytest.fixture def rng(): + if not _lib.RNG_ENABLED: + pytest.skip("RNG not enabled") return Random() @pytest.fixture( @@ -39,6 +41,15 @@ def rng(): def mldsa_type(request): return request.param + needs_make_key = pytest.mark.skipif(not _lib.ML_DSA_MAKE_KEY_ENABLED, + reason="ML-DSA key generation not enabled") + needs_sign = pytest.mark.skipif(not _lib.ML_DSA_SIGN_ENABLED, reason="ML-DSA signing not enabled") + needs_verify = pytest.mark.skipif(not _lib.ML_DSA_VERIFY_ENABLED, reason="ML-DSA verification not enabled") + needs_sizes = pytest.mark.skipif( + not (_lib.ML_DSA_PUBLIC_KEY_ENABLED and _lib.ML_DSA_PRIVATE_KEY_ENABLED + and (_lib.ML_DSA_SIGN_ENABLED or _lib.ML_DSA_VERIFY_ENABLED)), + reason="ML-DSA key or signature sizes not available") + def test_init_base(mldsa_type): mldsa_priv = MlDsaPrivate(mldsa_type) assert isinstance(mldsa_priv, MlDsaPrivate) @@ -46,6 +57,7 @@ def test_init_base(mldsa_type): mldsa_pub = MlDsaPublic(mldsa_type) assert isinstance(mldsa_pub, MlDsaPublic) + @needs_sizes def test_size_properties(mldsa_type): refvals = { MlDsaType.ML_DSA_44: { @@ -74,6 +86,7 @@ def test_size_properties(mldsa_type): assert mldsa_priv.pub_key_size == refvals[mldsa_type]["pub_key_size"] assert mldsa_priv.priv_key_size == refvals[mldsa_type]["priv_key_size"] + @needs_make_key def test_initializations(mldsa_type, rng): mldsa_priv = MlDsaPrivate.make_key(mldsa_type, rng) assert type(mldsa_priv) is MlDsaPrivate @@ -84,6 +97,7 @@ def test_initializations(mldsa_type, rng): mldsa_pub = MlDsaPublic(mldsa_type) assert type(mldsa_pub) is MlDsaPublic + @needs_make_key def test_key_import_export(mldsa_type, rng): # Generate key pair and export keys mldsa_priv = MlDsaPrivate.make_key(mldsa_type, rng) @@ -112,6 +126,9 @@ def test_key_import_export(mldsa_type, rng): pub_key3 = mldsa_pub.encode_key() assert pub_key == pub_key3 + @needs_make_key + @needs_sign + @needs_verify def test_sign_verify(mldsa_type, rng): # Generate a key pair and export public key mldsa_priv = MlDsaPrivate.make_key(mldsa_type, rng) @@ -176,6 +193,9 @@ def test_sign_verify(mldsa_type, rng): assert mldsa_pub.verify(signature, message, ctx=b"") @pytest.mark.skipif(not _lib.ML_DSA_NO_CTX_ENABLED, reason="Requires support for signing without context") + @needs_make_key + @needs_sign + @needs_verify def test_sign_with_seed(mldsa_type, rng): signature_seed = rng.bytes(ML_DSA_SIGNATURE_SEED_LENGTH) mldsa_priv = MlDsaPrivate.make_key(mldsa_type, rng) @@ -205,6 +225,8 @@ def test_sign_with_seed(mldsa_type, rng): with pytest.raises(TypeError): _ = mldsa_priv.sign_with_seed(message, "") # ty: ignore[invalid-argument-type] + @needs_make_key + @needs_sign def test_sign_with_seed_and_context(mldsa_type, rng): signature_seed = rng.bytes(ML_DSA_SIGNATURE_SEED_LENGTH) mldsa_priv = MlDsaPrivate.make_key(mldsa_type, rng) @@ -226,6 +248,8 @@ def test_sign_with_seed_and_context(mldsa_type, rng): signature_from_same_seed = mldsa_priv.sign_with_seed(message, signature_seed, ctx=context) assert signature == signature_from_same_seed + @needs_make_key + @needs_sign @pytest.mark.parametrize("seed", [0, "seed"]) def test_sign_with_seed_bad_type(mldsa_type, rng, seed: int | str): mldsa_priv = MlDsaPrivate.make_key(mldsa_type, rng) @@ -234,10 +258,12 @@ def test_sign_with_seed_bad_type(mldsa_type, rng, seed: int | str): with pytest.raises(TypeError): mldsa_priv.sign_with_seed(message, seed, ctx=context) + @needs_make_key def test_make_key_from_seed(mldsa_type): seed = bytes(MlDsaPrivate.ML_DSA_KEYGEN_SEED_LENGTH) assert MlDsaPrivate.make_key_from_seed(mldsa_type, seed) + @needs_make_key @pytest.mark.parametrize( "seed_length", [MlDsaPrivate.ML_DSA_KEYGEN_SEED_LENGTH - 1, MlDsaPrivate.ML_DSA_KEYGEN_SEED_LENGTH + 1] ) @@ -246,6 +272,7 @@ def test_make_key_from_seed_bad_length(mldsa_type, seed_length): with pytest.raises(ValueError): MlDsaPrivate.make_key_from_seed(mldsa_type, seed) + @needs_make_key @pytest.mark.parametrize("seed", [0, "seed"]) def test_make_key_from_seed_bad_type(mldsa_type, seed: int | str): with pytest.raises(TypeError): diff --git a/tests/test_mlkem.py b/tests/test_mlkem.py index 89ed2ba..0fc3a21 100644 --- a/tests/test_mlkem.py +++ b/tests/test_mlkem.py @@ -549,6 +549,16 @@ (MlKemType.ML_KEM_1024), ] + needs_make_key = pytest.mark.skipif(not _lib.ML_KEM_MAKE_KEY_ENABLED, + reason="ML-KEM key generation not enabled") + needs_encapsulate = pytest.mark.skipif(not _lib.ML_KEM_ENCAPSULATE_ENABLED, + reason="ML-KEM encapsulation not enabled") + needs_decapsulate = pytest.mark.skipif(not _lib.ML_KEM_DECAPSULATE_ENABLED, + reason="ML-KEM decapsulation not enabled") + + @pytest.mark.skipif(not _lib.RNG_ENABLED, reason="RNG not enabled") + @needs_encapsulate + @needs_decapsulate @pytest.mark.parametrize("mlkem_type", mlkem_types) def test_init_pattern_1(mlkem_type): mlkem_priv = MlKemPrivate(mlkem_type) @@ -573,6 +583,10 @@ def test_init_pattern_1(mlkem_type): assert ss_send == ref_ss[mlkem_type] assert ss_send == ss_recv + @pytest.mark.skipif(not _lib.RNG_ENABLED, reason="RNG not enabled") + @needs_make_key + @needs_encapsulate + @needs_decapsulate @pytest.mark.parametrize("mlkem_type", mlkem_types) def test_init_pattern_2(mlkem_type): mlkem_priv = MlKemPrivate.make_key_with_random( @@ -598,6 +612,10 @@ def test_init_pattern_2(mlkem_type): assert ss_send == ref_ss[mlkem_type] assert ss_send == ss_recv + @pytest.mark.skipif(not _lib.RNG_ENABLED, reason="RNG not enabled") + @needs_make_key + @needs_encapsulate + @needs_decapsulate @pytest.mark.parametrize("mlkem_type", mlkem_types) def test_init_pattern_3(mlkem_type): mlkem_priv = MlKemPrivate.make_key(mlkem_type) @@ -617,12 +635,14 @@ def test_init_pattern_3(mlkem_type): ss_recv = mlkem_priv.decapsulate(ct) assert ss_send == ss_recv + @needs_make_key @pytest.mark.parametrize("mlkem_type", mlkem_types) @pytest.mark.parametrize("rand", [0, "rand"]) def test_make_key_with_random_bad_random_type(mlkem_type, rand: int | str): with pytest.raises(TypeError): MlKemPrivate.make_key_with_random(mlkem_type, rand) + @needs_encapsulate @pytest.mark.parametrize("mlkem_type", mlkem_types) @pytest.mark.parametrize("rand", [0, "rand"]) def test_encapsulate_with_random_bad_random_type(mlkem_type, rand: int | str): diff --git a/tests/test_pwdbased.py b/tests/test_pwdbased.py index 2444860..ca3f693 100644 --- a/tests/test_pwdbased.py +++ b/tests/test_pwdbased.py @@ -22,10 +22,12 @@ # ty: ignore[possibly-missing-import] from collections import namedtuple +import importlib.util import pytest +from wolfcrypt import pwdbased from wolfcrypt._ffi import lib as _lib -if _lib.PWDBASED_ENABLED: +if _lib.PBKDF2_ENABLED: from wolfcrypt.pwdbased import PBKDF2 if _lib.SHA_ENABLED: @@ -41,7 +43,7 @@ def pbkdf2_vectors(): vectors = [] - if _lib.PWDBASED_ENABLED and _lib.SHA_ENABLED and _lib.HMAC_ENABLED: + if _lib.PBKDF2_ENABLED and _lib.SHA_ENABLED and _lib.HMAC_ENABLED: # HMAC requires a key, which in this case is the password. Do not # shorten the length of the password below the FIPS requirement. # See HMAC_FIPS_MIN_KEY. @@ -60,3 +62,14 @@ def test_pbkdf2(pbkdf2_vectors): key = PBKDF2(vector.password, vector.salt, vector.iterations, vector.key_length, vector.hash_type) assert len(key) == vector.key_length + +def test_pbkdf2_defined_only_when_enabled(monkeypatch): + """F-12232: PBKDF2 needs wc_PBKDF2 in the linked wolfSSL.""" + assert hasattr(pwdbased, "PBKDF2") == bool(_lib.PBKDF2_ENABLED) + + # Load a fresh copy of the module as if wc_PBKDF2 were not compiled in. + monkeypatch.setattr(_lib, "PBKDF2_ENABLED", 0) + spec = importlib.util.find_spec("wolfcrypt.pwdbased") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + assert not hasattr(module, "PBKDF2") diff --git a/tests/test_random.py b/tests/test_random.py index 4e5eed0..8c7f7fc 100644 --- a/tests/test_random.py +++ b/tests/test_random.py @@ -27,6 +27,8 @@ @pytest.fixture def rng(): + if not _lib.RNG_ENABLED: + pytest.skip("RNG not enabled") return Random() @@ -42,6 +44,8 @@ def test_bytes(rng): @pytest.fixture def rng_nonce(): + if not _lib.RNG_ENABLED: + pytest.skip("RNG not enabled") return Random(b"abcdefghijklmnopqrstuv") @@ -80,3 +84,10 @@ def test_reseed_multiple(rng): # Pull some bytes from the random number generator to test that it still works. rng.bytes(100) + + +def test_random_rejected_when_not_compiled_in(monkeypatch): + """F-13021: Random needs the RNG API in the linked wolfSSL.""" + monkeypatch.setattr(_lib, "RNG_ENABLED", 0) + with pytest.raises(NotImplementedError, match="RNG is not supported"): + Random() diff --git a/wolfcrypt/_ffi/lib.pyi b/wolfcrypt/_ffi/lib.pyi index ca50324..67ec7c6 100644 --- a/wolfcrypt/_ffi/lib.pyi +++ b/wolfcrypt/_ffi/lib.pyi @@ -24,31 +24,71 @@ from typing import TypeAlias INVALID_DEVID: int AES_ENABLED: int +AES_CBC_ENABLED: int +AES_CTR_ENABLED: int +AES_DECRYPT_ENABLED: int AES_SIV_ENABLED: int AESGCM_STREAM_ENABLED: int +AESGCM_STREAM_DECRYPT_ENABLED: int ASN_ENABLED: int CHACHA_ENABLED: int CHACHA_STREAM_ENABLED: int CHACHA20_POLY1305_ENABLED: int +DER_TO_PEM_ENABLED: int DES3_ENABLED: int ECC_ENABLED: int +ECC_DHE_ENABLED: int +ECC_KEY_EXPORT_ENABLED: int +ECC_KEY_IMPORT_ENABLED: int +ECC_SIGN_ENABLED: int +ECC_VERIFY_ENABLED: int ED25519_ENABLED: int +ED25519_KEY_EXPORT_ENABLED: int +ED25519_KEY_IMPORT_ENABLED: int +ED25519_MAKE_KEY_ENABLED: int +ED25519_SIGN_ENABLED: int +ED25519_VERIFY_ENABLED: int ED448_ENABLED: int +ED448_KEY_EXPORT_ENABLED: int +ED448_KEY_IMPORT_ENABLED: int +ED448_SIGN_ENABLED: int +ED448_VERIFY_ENABLED: int ERROR_STRINGS_ENABLED: int FIPS_ENABLED: int HMAC_ENABLED: int KEYGEN_ENABLED: int HKDF_ENABLED: int ML_DSA_ENABLED: int +ML_DSA_MAKE_KEY_ENABLED: int ML_DSA_NO_CTX_ENABLED: int +ML_DSA_PRIVATE_KEY_ENABLED: int +ML_DSA_PUBLIC_KEY_ENABLED: int +ML_DSA_SIGN_ENABLED: int +ML_DSA_VERIFY_ENABLED: int +ML_KEM_DECAPSULATE_ENABLED: int ML_KEM_ENABLED: int +ML_KEM_ENCAPSULATE_ENABLED: int +ML_KEM_MAKE_KEY_ENABLED: int MPAPI_ENABLED: int +PBKDF2_ENABLED: int +PEM_TO_DER_ENABLED: int +PKCS8_ENABLED: int PWDBASED_ENABLED: int +RNG_ENABLED: int RSA_ENABLED: int RSA_BLINDING_ENABLED: int +RSA_ENCRYPT_ENABLED: int +RSA_OAEP_ENABLED: int +RSA_PRIVATE_ENABLED: int RSA_PSS_ENABLED: int +RSA_SIGN_ENABLED: int +RSA_VERIFY_ENABLED: int SHA_ENABLED: int SHA3_ENABLED: int +SHA3_224_ENABLED: int +SHA3_256_ENABLED: int +SHA3_384_ENABLED: int +SHA3_512_ENABLED: int SHA256_ENABLED: int SHA384_ENABLED: int SHA512_ENABLED: int @@ -275,6 +315,8 @@ MIN_CODE_E: int FIPS_VERSION: int +ECC_PRIVATEKEY: int + WC_MGF1NONE: int WC_MGF1SHA1: int WC_MGF1SHA224: int @@ -299,6 +341,9 @@ WC_HASH_TYPE_SHA3_512: int WC_HASH_TYPE_BLAKE2B: int WC_HASH_TYPE_BLAKE2S: int +WC_MIN_DIGEST_SIZE: int +WC_MAX_DIGEST_SIZE: int + WC_ML_KEM_512: int WC_ML_KEM_768: int WC_ML_KEM_1024: int @@ -477,6 +522,7 @@ def wc_ecc_free(key: EccKey) -> int: ... def wc_ecc_size(key: EccKey) -> int: ... def wc_ecc_sig_size(key: EccKey) -> int: ... def wc_ecc_get_curve_size_from_id(curve_id: int) -> int: ... +def wc_ecc_check_key(key: EccKey) -> int: ... def wc_ecc_import_unsigned(key: EccKey, qx: bytes, qy: bytes, d: bytes | FFI.CData, curve_id: int) -> int: ... def wc_ecc_export_public_raw(key: EccKey, qx: BytePtr, qx_len: IntPtr, qy: BytePtr, qy_len: IntPtr) -> int: ... def wc_ecc_import_x963(x963: bytes, x963_len: int, key: EccKey) -> int: ... @@ -494,6 +540,7 @@ def wc_EccPublicKeyDecode(pub_der: bytes, in_out_idx: IntPtr, key: EccKey, pub_d def wc_EccPrivateKeyDecode(priv_der: bytes, in_out_idx: IntPtr, key: EccKey, priv_der_len: int) -> int: ... def wc_EccPublicKeyToDer(key: EccKey, pub_der: BytePtr, pub_der_len: int, with_alg_curve: int) -> int: ... def wc_EccKeyToDer(key: EccKey, priv_key_der: BytePtr, priv_key_len: int) -> int: ... +def wc_EccKeyDerSize(key: EccKey, pub: int) -> int: ... Ed25519Key: TypeAlias = FFI.CData diff --git a/wolfcrypt/asn.py b/wolfcrypt/asn.py index 395d413..83a8436 100644 --- a/wolfcrypt/asn.py +++ b/wolfcrypt/asn.py @@ -42,7 +42,7 @@ if _lib.SHA512_ENABLED: from wolfcrypt.hashes import Sha512 # ty: ignore[possibly-missing-import] -if _lib.ASN_ENABLED: +if _lib.PEM_TO_DER_ENABLED: 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, @@ -56,6 +56,7 @@ def pem_to_der(pem: bytes, pem_type: int) -> bytes: _lib.wc_FreeDer(der) return result +if _lib.DER_TO_PEM_ENABLED: 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) @@ -70,6 +71,7 @@ def der_to_pem(der: bytes, pem_type: int) -> bytes: return _ffi.buffer(pem, pem_length)[:] +if _lib.ASN_ENABLED: def hash_oid_from_class(hash_cls: type[_Hash]) -> int: if _lib.SHA_ENABLED and hash_cls == Sha: return _lib.SHAh diff --git a/wolfcrypt/ciphers.py b/wolfcrypt/ciphers.py index d66071e..c357120 100644 --- a/wolfcrypt/ciphers.py +++ b/wolfcrypt/ciphers.py @@ -38,7 +38,7 @@ from wolfcrypt.utils import BytesOrStr, t2b from .wc_types import SupportsRsaSign, SupportsRsaVerify -if _lib.ASN_ENABLED: +if _lib.PEM_TO_DER_ENABLED: from wolfcrypt.asn import pem_to_der # ty: ignore[possibly-missing-import] @@ -280,6 +280,20 @@ class Aes(_Cipher): _key_sizes = [16, 24, 32] _native_type = "Aes *" + @override + def __init__(self, key: BytesOrStr, mode: int, IV: BytesOrStr | None = None) -> None: + if mode == MODE_CTR and not _lib.AES_CTR_ENABLED: + raise NotImplementedError("AES-CTR is not supported by this wolfSSL build") + if mode == MODE_CBC and not _lib.AES_CBC_ENABLED: + raise NotImplementedError("AES-CBC is not supported by this wolfSSL build") + super().__init__(key, mode, IV) + + @override + def decrypt(self, string: BytesOrStr) -> bytes: + if self.mode == MODE_CBC and not _lib.AES_DECRYPT_ENABLED: + raise NotImplementedError("AES-CBC decryption is not supported by this wolfSSL build") + return super().decrypt(string) + @override def _set_key(self, direction: int) -> int: if direction == _ENCRYPTION: @@ -505,6 +519,8 @@ def decrypt(self, data: BytesOrStr) -> bytes: """ Add more data to the decryption stream """ + if not _lib.AESGCM_STREAM_DECRYPT_ENABLED: + raise NotImplementedError("AES-GCM streaming decryption is not supported by this wolfSSL build") aad = b"" data = t2b(data) if self._mode is None: @@ -635,11 +651,12 @@ def _decrypt(self, destination: _ffi.CData, source: bytes) -> int: _NONCE_SIZE = 12 def set_iv(self, nonce: BytesOrStr, counter: int = 0) -> None: - self._IV_nonce = t2b(nonce) - if len(self._IV_nonce) != self._NONCE_SIZE: - raise ValueError(f"nonce must be {self._NONCE_SIZE} bytes, got {len(self._IV_nonce)}") - self._IV_counter = counter self._iv_set = False + nonce = t2b(nonce) + if len(nonce) != self._NONCE_SIZE: + raise ValueError(f"nonce must be {self._NONCE_SIZE} bytes, got {len(nonce)}") + self._IV_nonce = nonce + self._IV_counter = counter ret = self._set_key(self._REKEY_BOTH) if ret < 0: raise WolfCryptApiError("ChaCha set_iv error", ret) @@ -767,15 +784,12 @@ class _Rsa: # pylint: disable=too-few-public-methods _hash_type = None def __init__(self, rng: Random | None = None) -> None: - if rng is None: - rng = Random() - self.native_object = _ffi.new("RsaKey *") ret = _lib.wc_InitRsaKey(self.native_object, _ffi.NULL) if ret < 0: # pragma: no cover raise WolfCryptApiError("Invalid key error", ret) - self._random = rng + self._rng = rng if _lib.RSA_BLINDING_ENABLED: ret = _lib.wc_RsaSetRNG(self.native_object, self._random.native_object) @@ -789,6 +803,13 @@ def __del__(self) -> None: if self.native_object: self._delete(self.native_object) + # Created on first use so verify works in builds without an RNG. + @property + def _random(self) -> Random: + if self._rng is None: + self._rng = Random() + return self._rng + def set_mgf(self, mgf: int) -> None: self._mgf = mgf @@ -827,55 +848,57 @@ def __init__(self, key: BytesOrStr, hash_type: int | None = None, rng: Random | if self.output_size <= 0: # pragma: no cover raise WolfCryptApiError("Invalid key error", self.output_size) - if _lib.ASN_ENABLED: + if _lib.PEM_TO_DER_ENABLED: @classmethod def from_pem(cls, file: bytes, hash_type: int | None = None, rng: Random | None = None) -> RsaPublic: der = pem_to_der(file, _lib.PUBLICKEY_TYPE) return cls(key=der, hash_type=hash_type, rng=rng) - def encrypt(self, plaintext: BytesOrStr) -> bytes: - """ - Encrypts **plaintext**, using the public key data in the - object. The plaintext's length must not be greater than: + if _lib.RSA_ENCRYPT_ENABLED: + def encrypt(self, plaintext: BytesOrStr) -> bytes: + """ + Encrypts **plaintext**, using the public key data in the + object. The plaintext's length must not be greater than: - **self.output_size - self.RSA_MIN_PAD_SIZE** + **self.output_size - self.RSA_MIN_PAD_SIZE** - Returns a string containing the ciphertext. - """ + Returns a string containing the ciphertext. + """ - plaintext = t2b(plaintext) - ciphertext = _ffi.new(f"byte[{self.output_size}]") + plaintext = t2b(plaintext) + ciphertext = _ffi.new(f"byte[{self.output_size}]") - ret = _lib.wc_RsaPublicEncrypt(plaintext, len(plaintext), - ciphertext, self.output_size, - self.native_object, - self._random.native_object) + ret = _lib.wc_RsaPublicEncrypt(plaintext, len(plaintext), + ciphertext, self.output_size, + self.native_object, + self._random.native_object) - if ret != self.output_size: # pragma: no cover - raise WolfCryptApiError("Encryption error", ret) + if ret != self.output_size: # pragma: no cover + raise WolfCryptApiError("Encryption error", ret) - return _ffi.buffer(ciphertext)[:] + return _ffi.buffer(ciphertext)[:] - def encrypt_oaep(self, plaintext: BytesOrStr, label: BytesOrStr = "") -> bytes: - if not self._hash_type: - raise WolfCryptError("Hash type not set. Cannot use OAEP padding without a hash type.") - plaintext = t2b(plaintext) - label = t2b(label) - ciphertext = _ffi.new(f"byte[{self.output_size}]") - if self._mgf is None: - self._get_mgf() - assert self._mgf is not None - ret = _lib.wc_RsaPublicEncrypt_ex(plaintext, len(plaintext), - ciphertext, self.output_size, - self.native_object, - self._random.native_object, - _lib.WC_RSA_OAEP_PAD, self._hash_type, - self._mgf, label, len(label)) + if _lib.RSA_ENCRYPT_ENABLED and _lib.RSA_OAEP_ENABLED: + def encrypt_oaep(self, plaintext: BytesOrStr, label: BytesOrStr = "") -> bytes: + if not self._hash_type: + raise WolfCryptError("Hash type not set. Cannot use OAEP padding without a hash type.") + plaintext = t2b(plaintext) + label = t2b(label) + ciphertext = _ffi.new(f"byte[{self.output_size}]") + if self._mgf is None: + self._get_mgf() + assert self._mgf is not None + ret = _lib.wc_RsaPublicEncrypt_ex(plaintext, len(plaintext), + ciphertext, self.output_size, + self.native_object, + self._random.native_object, + _lib.WC_RSA_OAEP_PAD, self._hash_type, + self._mgf, label, len(label)) - if ret != self.output_size: # pragma: no cover - raise WolfCryptApiError("Encryption error", ret) + if ret != self.output_size: # pragma: no cover + raise WolfCryptApiError("Encryption error", ret) - return _ffi.buffer(ciphertext)[:] + return _ffi.buffer(ciphertext)[:] @override def verify(self, signature: BytesOrStr) -> bytes: @@ -887,6 +910,8 @@ def verify(self, signature: BytesOrStr) -> bytes: Returns a string containing the plaintext. """ + if not _lib.RSA_VERIFY_ENABLED: + raise NotImplementedError("RSA verification is not supported by this wolfSSL build") signature = t2b(signature) plaintext = _ffi.new(f"byte[{self.output_size}]") @@ -942,7 +967,7 @@ def verify_pss(self, plaintext: BytesOrStr, signature: BytesOrStr) -> bool: class RsaPrivate(RsaPublic, SupportsRsaSign): - if _lib.KEYGEN_ENABLED: + if _lib.KEYGEN_ENABLED and _lib.RSA_PRIVATE_ENABLED: @classmethod def make_key(cls, size: int, rng: Random | None = None, hash_type: int | None = None) -> RsaPrivate: """ @@ -976,6 +1001,8 @@ def __init__(self, key: BytesOrStr | None = None, hash_type: int | None = None, ret = _lib.wc_RsaPrivateKeyDecode(key, idx, self.native_object, len(key)) if ret < 0: + if not _lib.PKCS8_ENABLED: + raise WolfCryptApiError("Invalid key error", ret) idx[0] = 0 # wc_GetPkcs8TraditionalOffset takes byte* (non-const) per # the wolfSSL public header, so route it through a CFFI- @@ -996,7 +1023,7 @@ def __init__(self, key: BytesOrStr | None = None, hash_type: int | None = None, if self.output_size <= 0: # pragma: no cover raise WolfCryptApiError("Invalid key size error", self.output_size) - if _lib.ASN_ENABLED: + if _lib.PEM_TO_DER_ENABLED: @override @classmethod def from_pem(cls, file: bytes, hash_type: int | None = None, rng: Random | None = None) -> RsaPrivate: @@ -1026,54 +1053,56 @@ def encode_key(self) -> tuple[bytes, bytes]: return _ffi.buffer(priv, privlen)[:], _ffi.buffer(pub, publen)[:] - def decrypt(self, ciphertext: BytesOrStr) -> bytes: - """ - Decrypts **ciphertext**, using the private key data in the - object. The ciphertext's length must be equal to: + if _lib.RSA_PRIVATE_ENABLED: + def decrypt(self, ciphertext: BytesOrStr) -> bytes: + """ + Decrypts **ciphertext**, using the private key data in the + object. The ciphertext's length must be equal to: - **self.output_size** + **self.output_size** - Returns a string containing the plaintext. - """ - ciphertext = t2b(ciphertext) - plaintext = _ffi.new(f"byte[{self.output_size}]") + Returns a string containing the plaintext. + """ + ciphertext = t2b(ciphertext) + plaintext = _ffi.new(f"byte[{self.output_size}]") - ret = _lib.wc_RsaPrivateDecrypt(ciphertext, len(ciphertext), - plaintext, self.output_size, - self.native_object) + ret = _lib.wc_RsaPrivateDecrypt(ciphertext, len(ciphertext), + plaintext, self.output_size, + self.native_object) - if ret < 0: # pragma: no cover - raise WolfCryptApiError("Decryption error", ret) + if ret < 0: # pragma: no cover + raise WolfCryptApiError("Decryption error", ret) - return _ffi.buffer(plaintext, ret)[:] + return _ffi.buffer(plaintext, ret)[:] - def decrypt_oaep(self, ciphertext: BytesOrStr, label: BytesOrStr = "") -> bytes: - """ - Decrypts **ciphertext**, using the private key data in the - object. The ciphertext's length must be equal to: + if _lib.RSA_PRIVATE_ENABLED and _lib.RSA_OAEP_ENABLED: + def decrypt_oaep(self, ciphertext: BytesOrStr, label: BytesOrStr = "") -> bytes: + """ + Decrypts **ciphertext**, using the private key data in the + object. The ciphertext's length must be equal to: - **self.output_size** + **self.output_size** - Returns a string containing the plaintext. - """ - if not self._hash_type: - raise WolfCryptError("Hash type not set. Cannot use OAEP padding without a hash type.") - ciphertext = t2b(ciphertext) - label = t2b(label) - plaintext = _ffi.new(f"byte[{self.output_size}]") - if self._mgf is None: - self._get_mgf() - assert self._mgf is not None - ret = _lib.wc_RsaPrivateDecrypt_ex(ciphertext, len(ciphertext), - plaintext, self.output_size, - self.native_object, - _lib.WC_RSA_OAEP_PAD, self._hash_type, - self._mgf, label, len(label)) + Returns a string containing the plaintext. + """ + if not self._hash_type: + raise WolfCryptError("Hash type not set. Cannot use OAEP padding without a hash type.") + ciphertext = t2b(ciphertext) + label = t2b(label) + plaintext = _ffi.new(f"byte[{self.output_size}]") + if self._mgf is None: + self._get_mgf() + assert self._mgf is not None + ret = _lib.wc_RsaPrivateDecrypt_ex(ciphertext, len(ciphertext), + plaintext, self.output_size, + self.native_object, + _lib.WC_RSA_OAEP_PAD, self._hash_type, + self._mgf, label, len(label)) - if ret < 0: # pragma: no cover - raise WolfCryptApiError("Decryption error", ret) + if ret < 0: # pragma: no cover + raise WolfCryptApiError("Decryption error", ret) - return _ffi.buffer(plaintext, ret)[:] + return _ffi.buffer(plaintext, ret)[:] @override def sign(self, plaintext: BytesOrStr) -> bytes: @@ -1085,6 +1114,8 @@ def sign(self, plaintext: BytesOrStr) -> bytes: Returns a string containing the signature. """ + if not _lib.RSA_SIGN_ENABLED: + raise NotImplementedError("RSA signing is not supported by this wolfSSL build") plaintext = t2b(plaintext) signature = _ffi.new(f"byte[{self.output_size}]") @@ -1098,7 +1129,7 @@ def sign(self, plaintext: BytesOrStr) -> bytes: return _ffi.buffer(signature, self.output_size)[:] - if _lib.RSA_PSS_ENABLED: + if _lib.RSA_PSS_ENABLED and _lib.RSA_SIGN_ENABLED: def sign_pss(self, plaintext: BytesOrStr) -> bytes: """ Signs **plaintext**, using the private key data in the object. @@ -1158,138 +1189,166 @@ def size(self) -> int: def max_signature_size(self) -> int: return _lib.wc_ecc_sig_size(self.native_object) + # SHA-1 and SHA-2 digest sizes that this wolfSSL build accepts. + _DIGEST_SIZES = tuple(size for size in (20, 28, 32, 48, 64) + if _lib.WC_MIN_DIGEST_SIZE <= size <= _lib.WC_MAX_DIGEST_SIZE) + + @classmethod + def _check_digest(cls, digest: BytesOrStr) -> bytes: + digest = t2b(digest) + if len(digest) not in cls._DIGEST_SIZES: + raise ValueError(f"digest must be one of {cls._DIGEST_SIZES} bytes, got {len(digest)}") + return digest + class EccPublic(_Ecc): def __init__(self, key: BytesOrStr | None = None) -> None: _Ecc.__init__(self) if key: + if not _lib.ECC_KEY_IMPORT_ENABLED: + raise NotImplementedError("ECC key import is not supported by this wolfSSL build") self.decode_key(key) - def decode_key(self, key: BytesOrStr) -> None: - """ - Decodes an ECC public key from an ASN sequence. - """ - key = t2b(key) + def _check_key(self) -> None: + ret = _lib.wc_ecc_check_key(self.native_object) + if ret != 0: + raise WolfCryptApiError("Key check error", ret) - idx = _ffi.new("word32*") - idx[0] = 0 + if _lib.ECC_KEY_IMPORT_ENABLED: + def decode_key(self, key: BytesOrStr) -> None: + """ + Decodes an ECC public key from an ASN sequence. + """ + key = t2b(key) - ret = _lib.wc_EccPublicKeyDecode(key, idx, - self.native_object, len(key)) - if ret < 0: - raise WolfCryptApiError("Key decode error", ret) - if self.size <= 0: # pragma: no cover - raise WolfCryptError(f"Key decode error ({self.size})") - if self.max_signature_size <= 0: # pragma: no cover - raise WolfCryptError(f"Key decode error ({self.max_signature_size})") + idx = _ffi.new("word32*") + idx[0] = 0 - def decode_key_raw(self, qx: BytesOrStr, qy: BytesOrStr, curve_id: int = ECC_SECP256R1) -> None: - """ - Decodes an ECC public key from its raw elements: (Qx,Qy) - """ - qx = t2b(qx) - qy = t2b(qy) - curve_size = _lib.wc_ecc_get_curve_size_from_id(curve_id) - if curve_size <= 0: - raise ValueError(f"Unknown ECC curve_id {curve_id}") - if len(qx) != curve_size or len(qy) != curve_size: - raise ValueError( - f"qx and qy must each be {curve_size} bytes for curve_id {curve_id}, got " - f"qx={len(qx)} qy={len(qy)}") - ret = _lib.wc_ecc_import_unsigned(self.native_object, qx, qy, - _ffi.NULL, curve_id) - if ret != 0: - raise WolfCryptApiError("Key decode error", ret) + ret = _lib.wc_EccPublicKeyDecode(key, idx, + self.native_object, len(key)) + if ret < 0: + raise WolfCryptApiError("Key decode error", ret) + if self.size <= 0: # pragma: no cover + raise WolfCryptError(f"Key decode error ({self.size})") + if self.max_signature_size <= 0: # pragma: no cover + raise WolfCryptError(f"Key decode error ({self.max_signature_size})") + self._check_key() - def encode_key(self, with_curve: bool = True) -> bytes: - """ - Encodes the ECC public key in an ASN sequence. + def decode_key_raw(self, qx: BytesOrStr, qy: BytesOrStr, curve_id: int = ECC_SECP256R1) -> None: + """ + Decodes an ECC public key from its raw elements: (Qx,Qy) + """ + qx = t2b(qx) + qy = t2b(qy) + curve_size = _lib.wc_ecc_get_curve_size_from_id(curve_id) + if curve_size <= 0: + raise ValueError(f"Unknown ECC curve_id {curve_id}") + if len(qx) != curve_size or len(qy) != curve_size: + raise ValueError( + f"qx and qy must each be {curve_size} bytes for curve_id {curve_id}, got " + f"qx={len(qx)} qy={len(qy)}") + ret = _lib.wc_ecc_import_unsigned(self.native_object, qx, qy, + _ffi.NULL, curve_id) + if ret != 0: + raise WolfCryptApiError("Key decode error", ret) + self._check_key() - Returns the encoded key. - """ - key = _ffi.new(f"byte[{self.size * 4}]") + if _lib.ECC_KEY_EXPORT_ENABLED: + def encode_key(self, with_curve: bool = True) -> bytes: + """ + Encodes the ECC public key in an ASN sequence. - ret = _lib.wc_EccPublicKeyToDer(self.native_object, key, len(key), - with_curve) - if ret <= 0: # pragma: no cover - raise WolfCryptApiError("Key encode error", ret) + Returns the encoded key. + """ + key = _ffi.new(f"byte[{self.size * 4}]") - return _ffi.buffer(key, ret)[:] + ret = _lib.wc_EccPublicKeyToDer(self.native_object, key, len(key), + with_curve) + if ret <= 0: # pragma: no cover + raise WolfCryptApiError("Key encode error", ret) - def encode_key_raw(self) -> tuple[bytes, bytes]: - """ - Encodes the ECC public key in its two raw elements + return _ffi.buffer(key, ret)[:] - Returns (Qx, Qy) - """ - Qx = _ffi.new(f"byte[{self.size}]") - Qy = _ffi.new(f"byte[{self.size}]") - qx_size = _ffi.new("word32[1]") - qy_size = _ffi.new("word32[1]") - qx_size[0] = self.size - qy_size[0] = self.size - - ret = _lib.wc_ecc_export_public_raw(self.native_object, Qx, - qx_size, Qy, qy_size) - if ret != 0: # pragma: no cover - raise WolfCryptApiError("Key encode error", ret) - - return _ffi.buffer(Qx, qx_size[0])[:], _ffi.buffer(Qy, - qy_size[0])[:] - - def import_x963(self, x963: bytes) -> None: - """ - Imports an ECC public key in ANSI X9.63 format. - """ - ret = _lib.wc_ecc_import_x963(x963, len(x963), self.native_object) - if ret != 0: - raise WolfCryptApiError("x963 import error", ret) + def encode_key_raw(self) -> tuple[bytes, bytes]: + """ + Encodes the ECC public key in its two raw elements - def export_x963(self) -> bytes: - """ - Exports the public key data of the object in ANSI X9.63 format. + Returns (Qx, Qy) + """ + Qx = _ffi.new(f"byte[{self.size}]") + Qy = _ffi.new(f"byte[{self.size}]") + qx_size = _ffi.new("word32[1]") + qy_size = _ffi.new("word32[1]") + qx_size[0] = self.size + qy_size[0] = self.size + + ret = _lib.wc_ecc_export_public_raw(self.native_object, Qx, + qx_size, Qy, qy_size) + if ret != 0: # pragma: no cover + raise WolfCryptApiError("Key encode error", ret) - Returns the exported key. - """ - x963 = _ffi.new(f"byte[{self.size * 4}]") - x963_size = _ffi.new("word32[1]") - x963_size[0] = self.size * 4 + return _ffi.buffer(Qx, qx_size[0])[:], _ffi.buffer(Qy, + qy_size[0])[:] - ret = _lib.wc_ecc_export_x963(self.native_object, x963, x963_size) - if ret != 0: # pragma: no cover - raise WolfCryptApiError("x963 export error", ret) + if _lib.ECC_KEY_IMPORT_ENABLED: + def import_x963(self, x963: bytes) -> None: + """ + Imports an ECC public key in ANSI X9.63 format. + """ + ret = _lib.wc_ecc_import_x963(x963, len(x963), self.native_object) + if ret != 0: + raise WolfCryptApiError("x963 import error", ret) + self._check_key() - return _ffi.buffer(x963, x963_size[0])[:] + if _lib.ECC_KEY_EXPORT_ENABLED: + def export_x963(self) -> bytes: + """ + Exports the public key data of the object in ANSI X9.63 format. - def verify(self, signature: bytes, data: BytesOrStr) -> bool: - """ - Verifies **signature**, using the public key data in the object. + Returns the exported key. + """ + x963 = _ffi.new(f"byte[{self.size * 4}]") + x963_size = _ffi.new("word32[1]") + x963_size[0] = self.size * 4 - Returns **True** in case of a valid signature, otherwise **False**. - """ - data = t2b(data) - status = _ffi.new("int[1]") + ret = _lib.wc_ecc_export_x963(self.native_object, x963, x963_size) + if ret != 0: # pragma: no cover + raise WolfCryptApiError("x963 export error", ret) - ret = _lib.wc_ecc_verify_hash(signature, len(signature), - data, len(data), - status, self.native_object) + return _ffi.buffer(x963, x963_size[0])[:] - if ret < 0: - raise WolfCryptApiError("Verify error", ret) + if _lib.ECC_VERIFY_ENABLED: + def verify(self, signature: bytes, data: BytesOrStr) -> bool: + """ + Verifies **signature**, using the public key data in the object. + **data** is the message digest, as in EccPrivate.sign(). + + Returns **True** in case of a valid signature, otherwise **False**. + """ + data = self._check_digest(data) + status = _ffi.new("int[1]") + + ret = _lib.wc_ecc_verify_hash(signature, len(signature), + data, len(data), + status, self.native_object) + + if ret < 0: + raise WolfCryptApiError("Verify error", ret) - return status[0] == 1 + return status[0] == 1 - if _lib.MPAPI_ENABLED: + if _lib.ECC_VERIFY_ENABLED and _lib.MPAPI_ENABLED: def verify_raw(self, R: bytes, S: bytes, data: BytesOrStr) -> bool: """ Verifies signature from its raw elements **R** and **S**, using - the public key data in the object. + the public key data in the object. **data** is the message + digest, as in EccPrivate.sign(). Returns **True** in case of a valid signature, otherwise **False**. """ - data = t2b(data) + data = self._check_digest(data) status = _ffi.new("int[1]") mpR = _ffi.new("mp_int[1]") mpS = _ffi.new("mp_int[1]") @@ -1352,141 +1411,155 @@ def make_key(cls, size: int, rng: Random | None = None) -> EccPrivate: return ecc - @override - def decode_key(self, key: BytesOrStr) -> None: - """ - Decodes an ECC private key from an ASN sequence. - """ - key = t2b(key) + if _lib.ECC_KEY_IMPORT_ENABLED: + @override + def decode_key(self, key: BytesOrStr) -> None: + """ + Decodes an ECC private key from an ASN sequence. + """ + key = t2b(key) - idx = _ffi.new("word32*") - idx[0] = 0 + idx = _ffi.new("word32*") + idx[0] = 0 - ret = _lib.wc_EccPrivateKeyDecode(key, idx, - self.native_object, len(key)) - if ret < 0: - raise WolfCryptApiError("Key decode error", ret) - if self.size <= 0: # pragma: no cover - raise WolfCryptError(f"Key decode error {self.size}") - if self.max_signature_size <= 0: # pragma: no cover - raise WolfCryptError(f"Key decode error ({self.max_signature_size})") + ret = _lib.wc_EccPrivateKeyDecode(key, idx, + self.native_object, len(key)) + if ret < 0: + raise WolfCryptApiError("Key decode error", ret) + if self.size <= 0: # pragma: no cover + raise WolfCryptError(f"Key decode error {self.size}") + if self.max_signature_size <= 0: # pragma: no cover + raise WolfCryptError(f"Key decode error ({self.max_signature_size})") + # The public key is optional in an ECPrivateKey. + if self.native_object.type == _lib.ECC_PRIVATEKEY: + self._check_key() - @override - def decode_key_raw(self, qx: BytesOrStr, qy: BytesOrStr, d: BytesOrStr, curve_id: int = ECC_SECP256R1) -> None: - """ - Decodes an ECC private key from its raw elements: public (Qx,Qy) - and private(d) - """ - qx = t2b(qx) - qy = t2b(qy) - d = t2b(d) - curve_size = _lib.wc_ecc_get_curve_size_from_id(curve_id) - if curve_size <= 0: - raise ValueError(f"Unknown ECC curve_id {curve_id}") - if (len(qx) != curve_size or len(qy) != curve_size - or len(d) != curve_size): - raise ValueError( - f"qx, qy and d must each be {curve_size} bytes for curve_id {curve_id}, got " - f"qx={len(qx)} qy={len(qy)} d={len(d)}") - ret = _lib.wc_ecc_import_unsigned(self.native_object, qx, qy, d, - curve_id) - if ret != 0: - raise WolfCryptApiError("Key decode error", ret) + @override + def decode_key_raw(self, qx: BytesOrStr, qy: BytesOrStr, d: BytesOrStr, curve_id: int = ECC_SECP256R1) -> None: + """ + Decodes an ECC private key from its raw elements: public (Qx,Qy) + and private(d) + """ + qx = t2b(qx) + qy = t2b(qy) + d = t2b(d) + curve_size = _lib.wc_ecc_get_curve_size_from_id(curve_id) + if curve_size <= 0: + raise ValueError(f"Unknown ECC curve_id {curve_id}") + if (len(qx) != curve_size or len(qy) != curve_size + or len(d) != curve_size): + raise ValueError( + f"qx, qy and d must each be {curve_size} bytes for curve_id {curve_id}, got " + f"qx={len(qx)} qy={len(qy)} d={len(d)}") + ret = _lib.wc_ecc_import_unsigned(self.native_object, qx, qy, d, + curve_id) + if ret != 0: + raise WolfCryptApiError("Key decode error", ret) + self._check_key() - @override - def encode_key(self) -> bytes: - """ - Encodes the ECC private key in an ASN sequence. + if _lib.ECC_KEY_EXPORT_ENABLED: + @override + def encode_key(self) -> bytes: + """ + Encodes the ECC private key in an ASN sequence. - Returns the encoded key. - """ - key = _ffi.new(f"byte[{self.size * 4}]") + Returns the encoded key. + """ + size = _lib.wc_EccKeyDerSize(self.native_object, 1) + if size <= 0: # pragma: no cover + raise WolfCryptApiError("Key encode error", size) + key = _ffi.new(f"byte[{size}]") - ret = _lib.wc_EccKeyToDer(self.native_object, key, len(key)) - if ret <= 0: # pragma: no cover - raise WolfCryptApiError("Key encode error", ret) + ret = _lib.wc_EccKeyToDer(self.native_object, key, size) + if ret <= 0: # pragma: no cover + raise WolfCryptApiError("Key encode error", ret) - return _ffi.buffer(key, ret)[:] + return _ffi.buffer(key, ret)[:] - @override - def encode_key_raw(self) -> tuple[bytes, bytes, bytes]: - """ - Encodes the ECC private key in its three raw elements + @override + def encode_key_raw(self) -> tuple[bytes, bytes, bytes]: + """ + Encodes the ECC private key in its three raw elements - Returns (Qx, Qy, d) - """ - Qx = _ffi.new(f"byte[{self.size}]") - Qy = _ffi.new(f"byte[{self.size}]") - d = _ffi.new(f"byte[{self.size}]") - qx_size = _ffi.new("word32[1]") - qy_size = _ffi.new("word32[1]") - d_size = _ffi.new("word32[1]") - qx_size[0] = self.size - qy_size[0] = self.size - d_size[0] = self.size - - ret = _lib.wc_ecc_export_private_raw(self.native_object, Qx, - qx_size, Qy, qy_size, d, d_size) - if ret != 0: # pragma: no cover - raise WolfCryptApiError("Key encode error", ret) - - return _ffi.buffer(Qx, qx_size[0])[:], _ffi.buffer(Qy, - qy_size[0])[:], _ffi.buffer(d, d_size[0])[:] - - 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. + Returns (Qx, Qy, d) + """ + Qx = _ffi.new(f"byte[{self.size}]") + Qy = _ffi.new(f"byte[{self.size}]") + d = _ffi.new(f"byte[{self.size}]") + qx_size = _ffi.new("word32[1]") + qy_size = _ffi.new("word32[1]") + d_size = _ffi.new("word32[1]") + qx_size[0] = self.size + qy_size[0] = self.size + d_size[0] = self.size + + ret = _lib.wc_ecc_export_private_raw(self.native_object, Qx, + qx_size, Qy, qy_size, d, d_size) + if ret != 0: # pragma: no cover + raise WolfCryptApiError("Key encode error", ret) - Returns the shared secret. - """ - shared_secret = _ffi.new(f"byte[{self.max_signature_size}]") - secret_size = _ffi.new("word32[1]") - secret_size[0] = self.max_signature_size + return _ffi.buffer(Qx, qx_size[0])[:], _ffi.buffer(Qy, + qy_size[0])[:], _ffi.buffer(d, d_size[0])[:] + + if _lib.ECC_DHE_ENABLED: + 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. - ret = _lib.wc_ecc_shared_secret(self.native_object, - peer.native_object, - shared_secret, secret_size) + Returns the shared secret. + """ + shared_secret = _ffi.new(f"byte[{self.max_signature_size}]") + secret_size = _ffi.new("word32[1]") + secret_size[0] = self.max_signature_size - if ret != 0: # pragma: no cover - raise WolfCryptApiError("Shared secret error", ret) + ret = _lib.wc_ecc_shared_secret(self.native_object, + peer.native_object, + shared_secret, secret_size) - return _ffi.buffer(shared_secret, secret_size[0])[:] + if ret != 0: # pragma: no cover + raise WolfCryptApiError("Shared secret error", ret) - def sign(self, plaintext: BytesOrStr, rng: Random | None = None) -> bytes: - """ - Signs **plaintext**, using the private key data in the object. + return _ffi.buffer(shared_secret, secret_size[0])[:] - Returns the signature. - """ - if rng is None: - rng = Random() - plaintext = t2b(plaintext) - signature = _ffi.new(f"byte[{self.max_signature_size}]") + if _lib.ECC_SIGN_ENABLED: + def sign(self, plaintext: BytesOrStr, rng: Random | None = None) -> bytes: + """ + Signs **plaintext**, using the private key data in the object. + **plaintext** is the message digest: a SHA-1 or SHA-2 hash of + the message, computed by the caller. - signature_size = _ffi.new("word32[1]") - signature_size[0] = self.max_signature_size + Returns the signature. + """ + if rng is None: + rng = Random() + plaintext = self._check_digest(plaintext) + signature = _ffi.new(f"byte[{self.max_signature_size}]") - ret = _lib.wc_ecc_sign_hash(plaintext, len(plaintext), - signature, signature_size, - rng.native_object, - self.native_object) + signature_size = _ffi.new("word32[1]") + signature_size[0] = self.max_signature_size - if ret != 0: # pragma: no cover - raise WolfCryptApiError("Signature error", ret) + ret = _lib.wc_ecc_sign_hash(plaintext, len(plaintext), + signature, signature_size, + rng.native_object, + self.native_object) - return _ffi.buffer(signature, signature_size[0])[:] + if ret != 0: # pragma: no cover + raise WolfCryptApiError("Signature error", ret) - if _lib.MPAPI_ENABLED: + return _ffi.buffer(signature, signature_size[0])[:] + + if _lib.ECC_SIGN_ENABLED and _lib.MPAPI_ENABLED: def sign_raw(self, plaintext: BytesOrStr, rng: Random | None = None) -> tuple[bytes, bytes]: """ Signs **plaintext**, using the private key data in the object. + **plaintext** is the message digest, as in sign(). Returns the signature in its two raw components r, s """ if rng is None: rng = Random() - plaintext = t2b(plaintext) + plaintext = self._check_digest(plaintext) R = _ffi.new("mp_int[1]") S = _ffi.new("mp_int[1]") @@ -1553,61 +1626,66 @@ def __init__(self, key: BytesOrStr | None = None) -> None: _Ed25519.__init__(self) if key: + if not _lib.ED25519_KEY_IMPORT_ENABLED: + raise NotImplementedError("Ed25519 key import is not supported by this wolfSSL build") self.decode_key(key) - def decode_key(self, key: BytesOrStr) -> None: - """ - Decodes an ED25519 public key - """ - key = t2b(key) - if len(key) < _lib.wc_ed25519_pub_size(self.native_object): - raise WolfCryptError("Key decode error: key too short") + if _lib.ED25519_KEY_IMPORT_ENABLED: + def decode_key(self, key: BytesOrStr) -> None: + """ + Decodes an ED25519 public key + """ + key = t2b(key) + if len(key) < _lib.wc_ed25519_pub_size(self.native_object): + raise WolfCryptError("Key decode error: key too short") - idx = _ffi.new("word32*") - idx[0] = 0 - ret = _lib.wc_ed25519_import_public(key, len(key), - self.native_object) - if ret < 0: - raise WolfCryptApiError("Key decode error", ret) - if self.size <= 0: # pragma: no cover - raise WolfCryptError(f"Key decode error ({self.size})") - if self.max_signature_size <= 0: # pragma: no cover - raise WolfCryptError(f"Key decode error ({self.max_signature_size})") + idx = _ffi.new("word32*") + idx[0] = 0 + ret = _lib.wc_ed25519_import_public(key, len(key), + self.native_object) + if ret < 0: + raise WolfCryptApiError("Key decode error", ret) + if self.size <= 0: # pragma: no cover + raise WolfCryptError(f"Key decode error ({self.size})") + if self.max_signature_size <= 0: # pragma: no cover + raise WolfCryptError(f"Key decode error ({self.max_signature_size})") - def encode_key(self) -> bytes: - """ - Encodes the ED25519 public key + if _lib.ED25519_KEY_EXPORT_ENABLED: + def encode_key(self) -> bytes: + """ + Encodes the ED25519 public key - Returns the encoded key. - """ - key = _ffi.new(f"byte[{self.size * 4}]") - size = _ffi.new("word32[1]") + Returns the encoded key. + """ + key = _ffi.new(f"byte[{self.size * 4}]") + size = _ffi.new("word32[1]") - size[0] = _lib.wc_ed25519_pub_size(self.native_object) + size[0] = _lib.wc_ed25519_pub_size(self.native_object) - ret = _lib.wc_ed25519_export_public(self.native_object, key, size) - if ret != 0: # pragma: no cover - raise WolfCryptApiError("Key encode error", ret) + ret = _lib.wc_ed25519_export_public(self.native_object, key, size) + if ret != 0: # pragma: no cover + raise WolfCryptApiError("Key encode error", ret) - return _ffi.buffer(key, size[0])[:] + return _ffi.buffer(key, size[0])[:] - def verify(self, signature: bytes, data: BytesOrStr) -> bool: - """ - Verifies **signature**, using the public key data in the object. + if _lib.ED25519_VERIFY_ENABLED: + def verify(self, signature: bytes, data: BytesOrStr) -> bool: + """ + Verifies **signature**, using the public key data in the object. - Returns **True** in case of a valid signature, otherwise **False**. - """ - data = t2b(data) - status = _ffi.new("int[1]") + Returns **True** in case of a valid signature, otherwise **False**. + """ + data = t2b(data) + status = _ffi.new("int[1]") - ret = _lib.wc_ed25519_verify_msg(signature, len(signature), - data, len(data), - status, self.native_object) + ret = _lib.wc_ed25519_verify_msg(signature, len(signature), + data, len(data), + status, self.native_object) - if ret < 0: - raise WolfCryptApiError("Verify error", ret) + if ret < 0: + raise WolfCryptApiError("Verify error", ret) - return status[0] == 1 + return status[0] == 1 @@ -1617,114 +1695,123 @@ def __init__(self, key: BytesOrStr | None = None, pub: bytes | None = None) -> N self._rng = None + if key and not _lib.ED25519_KEY_IMPORT_ENABLED: + raise NotImplementedError("Ed25519 key import is not supported by this wolfSSL build") if key and not pub: self.decode_key(key) if key and pub: self.decode_key(key,pub) - @classmethod - def make_key(cls, size: int, rng: Random | None = None) -> Ed25519Private: - """ - Generates a new key pair of desired length **size**. - """ - if rng is None: - rng = Random() - ed25519 = cls() + if _lib.ED25519_MAKE_KEY_ENABLED: + @classmethod + def make_key(cls, size: int, rng: Random | None = None) -> Ed25519Private: + """ + Generates a new key pair of desired length **size**. + """ + if rng is None: + rng = Random() + ed25519 = cls() - ret = _lib.wc_ed25519_make_key(rng.native_object, size, - ed25519.native_object) - if ret < 0: - raise WolfCryptApiError("Key generation error", ret) + ret = _lib.wc_ed25519_make_key(rng.native_object, size, + ed25519.native_object) + if ret < 0: + raise WolfCryptApiError("Key generation error", ret) - # Retain RNG reference defensively; wolfSSL may retain a pointer - # internally on some builds. - ed25519._rng = rng + # Retain RNG reference defensively; wolfSSL may retain a pointer + # internally on some builds. + ed25519._rng = rng - return ed25519 + return ed25519 - @override - def decode_key(self, key: BytesOrStr, pub: bytes | None = None) -> None: - """ - Decodes an ED25519 private + pub key - """ - key = t2b(key) + if _lib.ED25519_KEY_IMPORT_ENABLED: + @override + def decode_key(self, key: BytesOrStr, pub: bytes | None = None) -> None: + """ + Decodes an ED25519 private + pub key + """ + key = t2b(key) - if len(key) < _lib.wc_ed25519_priv_size(self.native_object)/2: - raise WolfCryptError("Key decode error: key too short") + if len(key) < _lib.wc_ed25519_priv_size(self.native_object)/2: + raise WolfCryptError("Key decode error: key too short") - idx = _ffi.new("word32*") - idx[0] = 0 - if pub: - ret = _lib.wc_ed25519_import_private_key(key, len(key), pub, - len(pub), self.native_object) - if ret < 0: - raise WolfCryptApiError("Key decode error", ret) - else: - ret = _lib.wc_ed25519_import_private_only(key, len(key), - self.native_object) - if ret < 0: - raise WolfCryptApiError("Key decode error", ret) - pubkey = _ffi.new(f"byte[{self.size * 4}]") - ret = _lib.wc_ed25519_make_public(self.native_object, pubkey, - self.size) - if ret < 0: - raise WolfCryptApiError("Public key generate error", ret) - ret = _lib.wc_ed25519_import_public(pubkey, self.size, - self.native_object) - if ret < 0: - raise WolfCryptApiError("Public key import error", ret) + idx = _ffi.new("word32*") + idx[0] = 0 + if pub: + ret = _lib.wc_ed25519_import_private_key(key, len(key), pub, + len(pub), self.native_object) + if ret < 0: + raise WolfCryptApiError("Key decode error", ret) + else: + # wolfSSL builds wc_ed25519_make_public only with key generation. + if not _lib.ED25519_MAKE_KEY_ENABLED: + raise NotImplementedError("Deriving the Ed25519 public key is not supported by this wolfSSL build") + ret = _lib.wc_ed25519_import_private_only(key, len(key), + self.native_object) + if ret < 0: + raise WolfCryptApiError("Key decode error", ret) + pubkey = _ffi.new(f"byte[{self.size * 4}]") + ret = _lib.wc_ed25519_make_public(self.native_object, pubkey, + self.size) + if ret < 0: + raise WolfCryptApiError("Public key generate error", ret) + ret = _lib.wc_ed25519_import_public(pubkey, self.size, + self.native_object) + if ret < 0: + raise WolfCryptApiError("Public key import error", ret) - if self.size <= 0: # pragma: no cover - raise WolfCryptError(f"Key decode error ({self.size})") - if self.max_signature_size <= 0: # pragma: no cover - raise WolfCryptError(f"Key decode error ({self.max_signature_size})") + if self.size <= 0: # pragma: no cover + raise WolfCryptError(f"Key decode error ({self.size})") + if self.max_signature_size <= 0: # pragma: no cover + raise WolfCryptError(f"Key decode error ({self.max_signature_size})") - @override - def encode_key(self) -> tuple[bytes, bytes]: - """ - Encodes the ED25519 private key. + if _lib.ED25519_KEY_EXPORT_ENABLED: + @override + def encode_key(self) -> tuple[bytes, bytes]: + """ + Encodes the ED25519 private key. - Returns the encoded key. - """ - key = _ffi.new(f"byte[{self.size * 4}]") - pubkey = _ffi.new(f"byte[{self.size * 4}]") - priv_size = _ffi.new("word32[1]") - pub_size = _ffi.new("word32[1]") + Returns the encoded key. + """ + key = _ffi.new(f"byte[{self.size * 4}]") + pubkey = _ffi.new(f"byte[{self.size * 4}]") + priv_size = _ffi.new("word32[1]") + pub_size = _ffi.new("word32[1]") - priv_size[0] = _lib.wc_ed25519_priv_size(self.native_object) - pub_size[0] = _lib.wc_ed25519_pub_size(self.native_object) + priv_size[0] = _lib.wc_ed25519_priv_size(self.native_object) + pub_size[0] = _lib.wc_ed25519_pub_size(self.native_object) - ret = _lib.wc_ed25519_export_private_only(self.native_object, - key, priv_size) - if ret != 0: # pragma: no cover - raise WolfCryptApiError("Private key encode error", ret) - ret = _lib.wc_ed25519_export_public(self.native_object, pubkey, - pub_size) - if ret != 0: # pragma: no cover - raise WolfCryptApiError("Public key encode error", ret) + ret = _lib.wc_ed25519_export_private_only(self.native_object, + key, priv_size) + if ret != 0: # pragma: no cover + raise WolfCryptApiError("Private key encode error", ret) + ret = _lib.wc_ed25519_export_public(self.native_object, pubkey, + pub_size) + if ret != 0: # pragma: no cover + raise WolfCryptApiError("Public key encode error", ret) - return _ffi.buffer(key, priv_size[0])[:], _ffi.buffer(pubkey, pub_size[0])[:] + return _ffi.buffer(key, priv_size[0])[:], _ffi.buffer(pubkey, pub_size[0])[:] - def sign(self, plaintext: BytesOrStr) -> bytes: - """ - Signs **plaintext**, using the private key data in the object. + if _lib.ED25519_SIGN_ENABLED: + def sign(self, plaintext: BytesOrStr) -> bytes: + """ + Signs **plaintext**, using the private key data in the object. - Returns the signature. - """ - plaintext = t2b(plaintext) - signature = _ffi.new(f"byte[{self.max_signature_size}]") + Returns the signature. + """ + plaintext = t2b(plaintext) + signature = _ffi.new(f"byte[{self.max_signature_size}]") - signature_size = _ffi.new("word32[1]") - signature_size[0] = self.max_signature_size + signature_size = _ffi.new("word32[1]") + signature_size[0] = self.max_signature_size - ret = _lib.wc_ed25519_sign_msg(plaintext, len(plaintext), - signature, signature_size, - self.native_object) + ret = _lib.wc_ed25519_sign_msg(plaintext, len(plaintext), + signature, signature_size, + self.native_object) - if ret != 0: # pragma: no cover - raise WolfCryptApiError("Signature error", ret) + if ret != 0: # pragma: no cover + raise WolfCryptApiError("Signature error", ret) - return _ffi.buffer(signature, signature_size[0])[:] + return _ffi.buffer(signature, signature_size[0])[:] if _lib.ED448_ENABLED: class _Ed448: # pylint: disable=too-few-public-methods @@ -1755,69 +1842,74 @@ def __init__(self, key: BytesOrStr | None = None) -> None: _Ed448.__init__(self) if key: + if not _lib.ED448_KEY_IMPORT_ENABLED: + raise NotImplementedError("Ed448 key import is not supported by this wolfSSL build") self.decode_key(key) - def decode_key(self, key: BytesOrStr) -> None: - """ - Decodes an ED448 public key - """ - key = t2b(key) - if len(key) < _lib.wc_ed448_pub_size(self.native_object): - raise WolfCryptError("Key decode error: key too short") + if _lib.ED448_KEY_IMPORT_ENABLED: + def decode_key(self, key: BytesOrStr) -> None: + """ + Decodes an ED448 public key + """ + key = t2b(key) + if len(key) < _lib.wc_ed448_pub_size(self.native_object): + raise WolfCryptError("Key decode error: key too short") - idx = _ffi.new("word32*") - idx[0] = 0 - ret = _lib.wc_ed448_import_public(key, len(key), - self.native_object) - if ret < 0: - raise WolfCryptApiError("Key decode error", ret) - if self.size <= 0: # pragma: no cover - raise WolfCryptError(f"Key decode error ({self.size})") - if self.max_signature_size <= 0: # pragma: no cover - raise WolfCryptError(f"Key decode error ({self.max_signature_size})") + idx = _ffi.new("word32*") + idx[0] = 0 + ret = _lib.wc_ed448_import_public(key, len(key), + self.native_object) + if ret < 0: + raise WolfCryptApiError("Key decode error", ret) + if self.size <= 0: # pragma: no cover + raise WolfCryptError(f"Key decode error ({self.size})") + if self.max_signature_size <= 0: # pragma: no cover + raise WolfCryptError(f"Key decode error ({self.max_signature_size})") - def encode_key(self) -> bytes: - """ - Encodes the ED448 public key + if _lib.ED448_KEY_EXPORT_ENABLED: + def encode_key(self) -> bytes: + """ + Encodes the ED448 public key - Returns the encoded key. - """ - key = _ffi.new(f"byte[{self.size * 4}]") - size = _ffi.new("word32[1]") + Returns the encoded key. + """ + key = _ffi.new(f"byte[{self.size * 4}]") + size = _ffi.new("word32[1]") - size[0] = _lib.wc_ed448_pub_size(self.native_object) + size[0] = _lib.wc_ed448_pub_size(self.native_object) - ret = _lib.wc_ed448_export_public(self.native_object, key, size) - if ret != 0: # pragma: no cover - raise WolfCryptApiError("Key encode error", ret) + ret = _lib.wc_ed448_export_public(self.native_object, key, size) + if ret != 0: # pragma: no cover + raise WolfCryptApiError("Key encode error", ret) - return _ffi.buffer(key, size[0])[:] + return _ffi.buffer(key, size[0])[:] - def verify(self, signature: bytes, data: BytesOrStr, ctx: BytesOrStr | None = None) -> bool: - """ - Verifies **signature**, using the public key data in the object. + if _lib.ED448_VERIFY_ENABLED: + def verify(self, signature: bytes, data: BytesOrStr, ctx: BytesOrStr | None = None) -> bool: + """ + Verifies **signature**, using the public key data in the object. - Returns **True** in case of a valid signature, otherwise **False**. - """ - data = t2b(data) - status = _ffi.new("int[1]") - ctx_buf = _ffi.NULL - ctx_buf_len = 0 - if ctx is not None: - ctx_buf = t2b(ctx) - ctx_buf_len = len(ctx_buf) - if ctx_buf_len > 255: - raise ValueError(f"Ed448 ctx must be at most 255 bytes, got {ctx_buf_len}") - - ret = _lib.wc_ed448_verify_msg(signature, len(signature), - data, len(data), status, - self.native_object, ctx_buf, - ctx_buf_len) + Returns **True** in case of a valid signature, otherwise **False**. + """ + data = t2b(data) + status = _ffi.new("int[1]") + ctx_buf = _ffi.NULL + ctx_buf_len = 0 + if ctx is not None: + ctx_buf = t2b(ctx) + ctx_buf_len = len(ctx_buf) + if ctx_buf_len > 255: + raise ValueError(f"Ed448 ctx must be at most 255 bytes, got {ctx_buf_len}") + + ret = _lib.wc_ed448_verify_msg(signature, len(signature), + data, len(data), status, + self.native_object, ctx_buf, + ctx_buf_len) - if ret < 0: - raise WolfCryptApiError("Verify error", ret) + if ret < 0: + raise WolfCryptApiError("Verify error", ret) - return status[0] == 1 + return status[0] == 1 @@ -1826,6 +1918,8 @@ def __init__(self, key: BytesOrStr | None = None, pub: bytes | None = None) -> N _Ed448.__init__(self) self._rng = None + if key and not _lib.ED448_KEY_IMPORT_ENABLED: + raise NotImplementedError("Ed448 key import is not supported by this wolfSSL build") if key and not pub: self.decode_key(key) if key and pub: @@ -1851,97 +1945,100 @@ def make_key(cls, size: int, rng: Random | None = None) -> Ed448Private: return ed448 - @override - def decode_key(self, key: BytesOrStr, pub: bytes | None = None) -> None: - """ - Decodes an ED448 private + pub key - """ - key = t2b(key) + if _lib.ED448_KEY_IMPORT_ENABLED: + @override + def decode_key(self, key: BytesOrStr, pub: bytes | None = None) -> None: + """ + Decodes an ED448 private + pub key + """ + key = t2b(key) - if len(key) < _lib.wc_ed448_priv_size(self.native_object)/2: - raise WolfCryptError("Key decode error: key too short") + if len(key) < _lib.wc_ed448_priv_size(self.native_object)/2: + raise WolfCryptError("Key decode error: key too short") - idx = _ffi.new("word32*") - idx[0] = 0 - if pub: - ret = _lib.wc_ed448_import_private_key(key, len(key), pub, - len(pub), self.native_object) - if ret < 0: - raise WolfCryptApiError("Key decode error", ret) - else: - ret = _lib.wc_ed448_import_private_only(key, len(key), - self.native_object) - if ret < 0: - raise WolfCryptApiError("Key decode error", ret) + idx = _ffi.new("word32*") + idx[0] = 0 + if pub: + ret = _lib.wc_ed448_import_private_key(key, len(key), pub, + len(pub), self.native_object) + if ret < 0: + raise WolfCryptApiError("Key decode error", ret) + else: + ret = _lib.wc_ed448_import_private_only(key, len(key), + self.native_object) + if ret < 0: + raise WolfCryptApiError("Key decode error", ret) + pubkey = _ffi.new(f"byte[{self.size * 4}]") + ret = _lib.wc_ed448_make_public(self.native_object, pubkey, + self.size) + if ret < 0: + raise WolfCryptApiError("Public key generate error", ret) + ret = _lib.wc_ed448_import_public(pubkey, self.size, + self.native_object) + if ret < 0: + raise WolfCryptApiError("Public key import error", ret) + + if self.size <= 0: # pragma: no cover + raise WolfCryptError(f"Key decode error ({self.size})") + if self.max_signature_size <= 0: # pragma: no cover + raise WolfCryptError(f"Key decode error ({self.max_signature_size})") + + if _lib.ED448_KEY_EXPORT_ENABLED: + @override + def encode_key(self) -> tuple[bytes, bytes]: + """ + Encodes the ED448 private key. + + Returns the encoded key. + """ + key = _ffi.new(f"byte[{self.size * 4}]") pubkey = _ffi.new(f"byte[{self.size * 4}]") - ret = _lib.wc_ed448_make_public(self.native_object, pubkey, - self.size) - if ret < 0: - raise WolfCryptApiError("Public key generate error", ret) - ret = _lib.wc_ed448_import_public(pubkey, self.size, - self.native_object) - if ret < 0: - raise WolfCryptApiError("Public key import error", ret) + priv_size = _ffi.new("word32[1]") + pub_size = _ffi.new("word32[1]") - if self.size <= 0: # pragma: no cover - raise WolfCryptError(f"Key decode error ({self.size})") - if self.max_signature_size <= 0: # pragma: no cover - raise WolfCryptError(f"Key decode error ({self.max_signature_size})") + priv_size[0] = _lib.wc_ed448_priv_size(self.native_object) + pub_size[0] = _lib.wc_ed448_pub_size(self.native_object) - @override - def encode_key(self) -> tuple[bytes, bytes]: - """ - Encodes the ED448 private key. + ret = _lib.wc_ed448_export_private_only(self.native_object, + key, priv_size) + if ret != 0: # pragma: no cover + raise WolfCryptApiError("Private key encode error", ret) + ret = _lib.wc_ed448_export_public(self.native_object, pubkey, + pub_size) + if ret != 0: # pragma: no cover + raise WolfCryptApiError("Public key encode error", ret) - Returns the encoded key. - """ - key = _ffi.new(f"byte[{self.size * 4}]") - pubkey = _ffi.new(f"byte[{self.size * 4}]") - priv_size = _ffi.new("word32[1]") - pub_size = _ffi.new("word32[1]") - - priv_size[0] = _lib.wc_ed448_priv_size(self.native_object) - pub_size[0] = _lib.wc_ed448_pub_size(self.native_object) - - ret = _lib.wc_ed448_export_private_only(self.native_object, - key, priv_size) - if ret != 0: # pragma: no cover - raise WolfCryptApiError("Private key encode error", ret) - ret = _lib.wc_ed448_export_public(self.native_object, pubkey, - pub_size) - if ret != 0: # pragma: no cover - raise WolfCryptApiError("Public key encode error", ret) - - return _ffi.buffer(key, priv_size[0])[:], _ffi.buffer(pubkey, pub_size[0])[:] - - def sign(self, plaintext: BytesOrStr, ctx : BytesOrStr | None = None) -> bytes: - """ - Signs **plaintext**, using the private key data in the object. + return _ffi.buffer(key, priv_size[0])[:], _ffi.buffer(pubkey, pub_size[0])[:] - Returns the signature. - """ - plaintext = t2b(plaintext) - signature = _ffi.new(f"byte[{self.max_signature_size}]") - - signature_size = _ffi.new("word32[1]") - signature_size[0] = self.max_signature_size - ctx_buf = _ffi.NULL - ctx_buf_len = 0 - if ctx is not None: - ctx_buf = t2b(ctx) - ctx_buf_len = len(ctx_buf) - if ctx_buf_len > 255: - raise ValueError(f"Ed448 ctx must be at most 255 bytes, got {ctx_buf_len}") - - ret = _lib.wc_ed448_sign_msg(plaintext, len(plaintext), - signature, signature_size, - self.native_object, ctx_buf, - ctx_buf_len) - - if ret != 0: # pragma: no cover - raise WolfCryptApiError("Signature error", ret) + if _lib.ED448_SIGN_ENABLED: + def sign(self, plaintext: BytesOrStr, ctx : BytesOrStr | None = None) -> bytes: + """ + Signs **plaintext**, using the private key data in the object. + + Returns the signature. + """ + plaintext = t2b(plaintext) + signature = _ffi.new(f"byte[{self.max_signature_size}]") + + signature_size = _ffi.new("word32[1]") + signature_size[0] = self.max_signature_size + ctx_buf = _ffi.NULL + ctx_buf_len = 0 + if ctx is not None: + ctx_buf = t2b(ctx) + ctx_buf_len = len(ctx_buf) + if ctx_buf_len > 255: + raise ValueError(f"Ed448 ctx must be at most 255 bytes, got {ctx_buf_len}") + + ret = _lib.wc_ed448_sign_msg(plaintext, len(plaintext), + signature, signature_size, + self.native_object, ctx_buf, + ctx_buf_len) + + if ret != 0: # pragma: no cover + raise WolfCryptApiError("Signature error", ret) - return _ffi.buffer(signature, signature_size[0])[:] + return _ffi.buffer(signature, signature_size[0])[:] if _lib.ML_KEM_ENABLED: @@ -2063,105 +2160,107 @@ def decode_key(self, pub_key: BytesOrStr) -> None: if ret < 0: # pragma: no cover raise WolfCryptApiError("wc_KyberKey_DecodePublicKey() error", ret) - def encapsulate(self, rng: Random | None = None) -> tuple[bytes, bytes]: - """ - :param rng: random number generator for an encupsulation - :type rng: Random - :return: tuple of a shared secret (first element) and the cipher text (second element) - :rtype: tuple[bytes, bytes] - """ - if rng is None: - rng = Random() - ct_size = self.ct_size - ss_size = self.ss_size - ct = _ffi.new(f"unsigned char[{ct_size}]") - ss = _ffi.new(f"unsigned char[{ss_size}]") - ret = _lib.wc_KyberKey_Encapsulate( - self.native_object, ct, ss, rng.native_object - ) - - if ret < 0: # pragma: no cover - raise WolfCryptApiError("wc_KyberKey_Encapsulate() error", ret) - - return _ffi.buffer(ss, ss_size)[:], _ffi.buffer(ct, ct_size)[:] + if _lib.ML_KEM_ENCAPSULATE_ENABLED: + def encapsulate(self, rng: Random | None = None) -> tuple[bytes, bytes]: + """ + :param rng: random number generator for an encupsulation + :type rng: Random + :return: tuple of a shared secret (first element) and the cipher text (second element) + :rtype: tuple[bytes, bytes] + """ + if rng is None: + rng = Random() + ct_size = self.ct_size + ss_size = self.ss_size + ct = _ffi.new(f"unsigned char[{ct_size}]") + ss = _ffi.new(f"unsigned char[{ss_size}]") + ret = _lib.wc_KyberKey_Encapsulate( + self.native_object, ct, ss, rng.native_object + ) - def encapsulate_with_random(self, rand: bytes) -> tuple[bytes, bytes]: - """ - :param rand: random number for an encapsulation - :type rand: bytes - :return: tuple of a shared secret (first element) and the cipher text (second element) - :rtype: tuple[bytes, bytes] - """ + if ret < 0: # pragma: no cover + raise WolfCryptApiError("wc_KyberKey_Encapsulate() error", ret) - try: - memoryview(rand) - except TypeError as exception: - raise TypeError("rand must support the buffer protocol, such as `bytes` or `bytearray`") from exception + return _ffi.buffer(ss, ss_size)[:], _ffi.buffer(ct, ct_size)[:] - rand = bytes(rand) + def encapsulate_with_random(self, rand: bytes) -> tuple[bytes, bytes]: + """ + :param rand: random number for an encapsulation + :type rand: bytes + :return: tuple of a shared secret (first element) and the cipher text (second element) + :rtype: tuple[bytes, bytes] + """ - ct_size = self.ct_size - ss_size = self.ss_size - ct = _ffi.new(f"unsigned char[{ct_size}]") - ss = _ffi.new(f"unsigned char[{ss_size}]") - ret = _lib.wc_KyberKey_EncapsulateWithRandom( - self.native_object, ct, ss, rand, len(rand) - ) + try: + memoryview(rand) + except TypeError as exception: + raise TypeError("rand must support the buffer protocol, such as `bytes` or `bytearray`") from exception + + rand = bytes(rand) + + ct_size = self.ct_size + ss_size = self.ss_size + ct = _ffi.new(f"unsigned char[{ct_size}]") + ss = _ffi.new(f"unsigned char[{ss_size}]") + ret = _lib.wc_KyberKey_EncapsulateWithRandom( + self.native_object, ct, ss, rand, len(rand) + ) - if ret < 0: # pragma: no cover - raise WolfCryptApiError("wc_KyberKey_EncapsulateWithRandom() error", ret) + if ret < 0: # pragma: no cover + raise WolfCryptApiError("wc_KyberKey_EncapsulateWithRandom() error", ret) - return _ffi.buffer(ss, ss_size)[:], _ffi.buffer(ct, ct_size)[:] + return _ffi.buffer(ss, ss_size)[:], _ffi.buffer(ct, ct_size)[:] class MlKemPrivate(_MlKemBase): - @classmethod - def make_key(cls, mlkem_type: MlKemType, rng: Random | None = None) -> MlKemPrivate: - """ - :param mlkem_type: ML-KEM type - :type mlkem_type: MlKemType - :param rng: random number generator for a key generation - :type rng: Random - :return: `MlKemPrivate` object - :rtype: MlKemPrivate - """ - if rng is None: - rng = Random() - mlkem_priv = cls(mlkem_type) - ret = _lib.wc_KyberKey_MakeKey(mlkem_priv.native_object, rng.native_object) + if _lib.ML_KEM_MAKE_KEY_ENABLED: + @classmethod + def make_key(cls, mlkem_type: MlKemType, rng: Random | None = None) -> MlKemPrivate: + """ + :param mlkem_type: ML-KEM type + :type mlkem_type: MlKemType + :param rng: random number generator for a key generation + :type rng: Random + :return: `MlKemPrivate` object + :rtype: MlKemPrivate + """ + if rng is None: + rng = Random() + mlkem_priv = cls(mlkem_type) + ret = _lib.wc_KyberKey_MakeKey(mlkem_priv.native_object, rng.native_object) - if ret < 0: # pragma: no cover - raise WolfCryptApiError("wc_KyberKey_MakeKey() error", ret) + if ret < 0: # pragma: no cover + raise WolfCryptApiError("wc_KyberKey_MakeKey() error", ret) - # Retain RNG reference defensively. - mlkem_priv._rng = rng + # Retain RNG reference defensively. + mlkem_priv._rng = rng - return mlkem_priv + return mlkem_priv - @classmethod - def make_key_with_random(cls, mlkem_type: MlKemType, rand: bytes) -> MlKemPrivate: - """ - :param mlkem_type: ML-KEM type - :type mlkem_type: MlKemType - :param rand: random number for a key generation - :type rand: bytes - :return: `MlKemPrivate` object - :rtype: MlKemPrivate - """ - mlkem_priv = cls(mlkem_type) + @classmethod + def make_key_with_random(cls, mlkem_type: MlKemType, rand: bytes) -> MlKemPrivate: + """ + :param mlkem_type: ML-KEM type + :type mlkem_type: MlKemType + :param rand: random number for a key generation + :type rand: bytes + :return: `MlKemPrivate` object + :rtype: MlKemPrivate + """ + mlkem_priv = cls(mlkem_type) - try: - memoryview(rand) - except TypeError as exception: - raise TypeError("rand must support the buffer protocol, such as `bytes` or `bytearray`") from exception + try: + memoryview(rand) + except TypeError as exception: + raise TypeError("rand must support the buffer protocol, such as `bytes` or `bytearray`") from exception - rand = bytes(rand) + rand = bytes(rand) - ret = _lib.wc_KyberKey_MakeKeyWithRandom(mlkem_priv.native_object, rand, len(rand)) + ret = _lib.wc_KyberKey_MakeKeyWithRandom(mlkem_priv.native_object, rand, len(rand)) - if ret < 0: # pragma: no cover - raise WolfCryptApiError("wc_KyberKey_MakeKeyWithRandom() error", ret) + if ret < 0: # pragma: no cover + raise WolfCryptApiError("wc_KyberKey_MakeKeyWithRandom() error", ret) - return mlkem_priv + return mlkem_priv @property def pub_key_size(self) -> int: @@ -2223,27 +2322,28 @@ def decode_key(self, priv_key: BytesOrStr) -> None: if ret < 0: # pragma: no cover raise WolfCryptApiError("wc_KyberKey_DecodePrivateKey() error", ret) - def decapsulate(self, ct: BytesOrStr) -> bytes: - """ - :param ct: cipher text - :type ct: bytes or str - :return: shared secret - :rtype: bytes - """ - ss_size = self.ss_size - ss = _ffi.new(f"unsigned char[{ss_size}]") - ct_bytestype = t2b(ct) - ret = _lib.wc_KyberKey_Decapsulate( - self.native_object, - ss, - ct_bytestype, - len(ct_bytestype), - ) + if _lib.ML_KEM_DECAPSULATE_ENABLED: + def decapsulate(self, ct: BytesOrStr) -> bytes: + """ + :param ct: cipher text + :type ct: bytes or str + :return: shared secret + :rtype: bytes + """ + ss_size = self.ss_size + ss = _ffi.new(f"unsigned char[{ss_size}]") + ct_bytestype = t2b(ct) + ret = _lib.wc_KyberKey_Decapsulate( + self.native_object, + ss, + ct_bytestype, + len(ct_bytestype), + ) - if ret < 0: # pragma: no cover - raise WolfCryptApiError("wc_KyberKey_Decapsulate() error", ret) + if ret < 0: # pragma: no cover + raise WolfCryptApiError("wc_KyberKey_Decapsulate() error", ret) - return _ffi.buffer(ss, ss_size)[:] + return _ffi.buffer(ss, ss_size)[:] if _lib.ML_DSA_ENABLED: @@ -2293,375 +2393,387 @@ def __del__(self) -> None: if self._init_done: _lib.wc_dilithium_free(self.native_object) - @property - def _pub_key_size(self) -> int: - size = _ffi.new("int *") - ret = _lib.wc_MlDsaKey_GetPubLen(self.native_object, size) + if _lib.ML_DSA_PUBLIC_KEY_ENABLED: + @property + def _pub_key_size(self) -> int: + size = _ffi.new("int *") + ret = _lib.wc_MlDsaKey_GetPubLen(self.native_object, size) - if ret < 0: # pragma: no cover - raise WolfCryptApiError("wc_MlDsaKey_GetPubLen() error", ret) + if ret < 0: # pragma: no cover + raise WolfCryptApiError("wc_MlDsaKey_GetPubLen() error", ret) - return size[0] + return size[0] - @property - def sig_size(self) -> int: - """ - :return: signature size in bytes - :rtype: int - """ - size = _ffi.new("int *") - ret = _lib.wc_MlDsaKey_GetSigLen(self.native_object, size) + def _decode_pub_key(self, pub_key: BytesOrStr) -> None: + pub_key_bytestype = t2b(pub_key) + ret = _lib.wc_dilithium_import_public( + pub_key_bytestype, + len(pub_key_bytestype), + self.native_object, + ) - if ret < 0: # pragma: no cover - raise WolfCryptApiError("wc_MlDsaKey_GetSigLen() error", ret) + if ret < 0: # pragma: no cover + raise WolfCryptApiError("wc_dilithium_import_public() error", ret) - return size[0] + def _encode_pub_key(self) -> bytes: + in_size = self._pub_key_size + pub_key = _ffi.new(f"byte[{in_size}]") + out_size = _ffi.new("word32 *") + out_size[0] = in_size + ret = _lib.wc_dilithium_export_public(self.native_object, pub_key, out_size) - def _decode_pub_key(self, pub_key: BytesOrStr) -> None: - pub_key_bytestype = t2b(pub_key) - ret = _lib.wc_dilithium_import_public( - pub_key_bytestype, - len(pub_key_bytestype), - self.native_object, - ) - - if ret < 0: # pragma: no cover - raise WolfCryptApiError("wc_dilithium_import_public() error", ret) - - def _encode_pub_key(self) -> bytes: - in_size = self._pub_key_size - pub_key = _ffi.new(f"byte[{in_size}]") - out_size = _ffi.new("word32 *") - out_size[0] = in_size - ret = _lib.wc_dilithium_export_public(self.native_object, pub_key, out_size) + if ret < 0: # pragma: no cover + raise WolfCryptApiError("wc_dilithium_export_public() error", ret) - if ret < 0: # pragma: no cover - raise WolfCryptApiError("wc_dilithium_export_public() error", ret) + if in_size != out_size[0]: + raise WolfCryptError(f"{in_size=} and {out_size[0]=} don't match") - if in_size != out_size[0]: - raise WolfCryptError(f"{in_size=} and {out_size[0]=} don't match") + return _ffi.buffer(pub_key, out_size[0])[:] - return _ffi.buffer(pub_key, out_size[0])[:] + if _lib.ML_DSA_SIGN_ENABLED or _lib.ML_DSA_VERIFY_ENABLED: + @property + def sig_size(self) -> int: + """ + :return: signature size in bytes + :rtype: int + """ + size = _ffi.new("int *") + ret = _lib.wc_MlDsaKey_GetSigLen(self.native_object, size) - def verify(self, signature: BytesOrStr, message: BytesOrStr, ctx: BytesOrStr | None = None) -> bool: - """ - :param signature: signature to be verified - :type signature: bytes or str - :param message: message to be verified - :type message: bytes or str - :param ctx: context, maximum 255 bytes (optional by default but that requires support for no-context - signing/verification compiled in; pass empty string "" for FIPS-204 empty-context verification). - :type ctx: bytes or str. None for no-context verification. - :return: True if the verification is successful, False otherwise - :rtype: bool - """ - if ctx is None and not _lib.ML_DSA_NO_CTX_ENABLED: - raise WolfCryptError("support for verifying without context is disabled") + if ret < 0: # pragma: no cover + raise WolfCryptApiError("wc_MlDsaKey_GetSigLen() error", ret) - sig_bytestype = t2b(signature) - msg_bytestype = t2b(message) - res = _ffi.new("int *") + return size[0] - if ctx is not None: - ctx_bytestype = t2b(ctx) - if len(ctx_bytestype) > 255: - raise ValueError( - f"context length {len(ctx_bytestype)} too large: must be 255 or less" + if _lib.ML_DSA_VERIFY_ENABLED: + def verify(self, signature: BytesOrStr, message: BytesOrStr, ctx: BytesOrStr | None = None) -> bool: + """ + :param signature: signature to be verified + :type signature: bytes or str + :param message: message to be verified + :type message: bytes or str + :param ctx: context, maximum 255 bytes (optional by default but that requires support for no-context + signing/verification compiled in; pass empty string "" for FIPS-204 empty-context verification). + :type ctx: bytes or str. None for no-context verification. + :return: True if the verification is successful, False otherwise + :rtype: bool + """ + if ctx is None and not _lib.ML_DSA_NO_CTX_ENABLED: + raise WolfCryptError("support for verifying without context is disabled") + + sig_bytestype = t2b(signature) + msg_bytestype = t2b(message) + res = _ffi.new("int *") + + if ctx is not None: + ctx_bytestype = t2b(ctx) + if len(ctx_bytestype) > 255: + raise ValueError( + f"context length {len(ctx_bytestype)} too large: must be 255 or less" + ) + ret = _lib.wc_dilithium_verify_ctx_msg( + sig_bytestype, + len(sig_bytestype), + ctx_bytestype, + len(ctx_bytestype), + msg_bytestype, + len(msg_bytestype), + res, + self.native_object, ) - ret = _lib.wc_dilithium_verify_ctx_msg( - sig_bytestype, - len(sig_bytestype), - ctx_bytestype, - len(ctx_bytestype), - msg_bytestype, - len(msg_bytestype), - res, - self.native_object, - ) - if ret < 0: # pragma: no cover - raise WolfCryptApiError("wc_dilithium_verify_ctx_msg() error", ret) - else: - ret = _lib.wc_dilithium_verify_msg( - sig_bytestype, - len(sig_bytestype), - msg_bytestype, - len(msg_bytestype), - res, - self.native_object, - ) - if ret < 0: # pragma: no cover - raise WolfCryptApiError("wc_dilithium_verify_msg() error", ret) + if ret < 0: # pragma: no cover + raise WolfCryptApiError("wc_dilithium_verify_ctx_msg() error", ret) + else: + ret = _lib.wc_dilithium_verify_msg( + sig_bytestype, + len(sig_bytestype), + msg_bytestype, + len(msg_bytestype), + res, + self.native_object, + ) + if ret < 0: # pragma: no cover + raise WolfCryptApiError("wc_dilithium_verify_msg() error", ret) - return res[0] == 1 + return res[0] == 1 class MlDsaPrivate(_MlDsaBase): - @classmethod - def make_key(cls, mldsa_type: MlDsaType, rng: Random | None = None) -> MlDsaPrivate: - """ - :param mldsa_type: ML-DSA type - :type mldsa_type: MlDsaType - :param rng: random number generator for a key generation - :type rng: Random - :return: `MlDsaPrivate` object - :rtype: MlDsaPrivate - """ - if rng is None: - rng = Random() - mldsa_priv = cls(mldsa_type) - ret = _lib.wc_dilithium_make_key( - mldsa_priv.native_object, rng.native_object - ) - - if ret < 0: # pragma: no cover - raise WolfCryptApiError("wc_dilithium_make_key() error", ret) - - # Retain RNG reference defensively. - mldsa_priv._rng = rng - - return mldsa_priv + if _lib.ML_DSA_MAKE_KEY_ENABLED: + @classmethod + def make_key(cls, mldsa_type: MlDsaType, rng: Random | None = None) -> MlDsaPrivate: + """ + :param mldsa_type: ML-DSA type + :type mldsa_type: MlDsaType + :param rng: random number generator for a key generation + :type rng: Random + :return: `MlDsaPrivate` object + :rtype: MlDsaPrivate + """ + if rng is None: + rng = Random() + mldsa_priv = cls(mldsa_type) + ret = _lib.wc_dilithium_make_key( + mldsa_priv.native_object, rng.native_object + ) - @classmethod - def make_key_from_seed(cls, mldsa_type: MlDsaType, seed: bytes) -> MlDsaPrivate: - """ - Deterministically generate the key from a seed. + if ret < 0: # pragma: no cover + raise WolfCryptApiError("wc_dilithium_make_key() error", ret) - :param mldsa_type: ML-DSA type - :type mldsa_type: MlDsaType - :param seed: the (32 byte) seed from which to deterministically create the key - :type seed: bytes - """ - mldsa_priv = cls(mldsa_type) + # Retain RNG reference defensively. + mldsa_priv._rng = rng - try: - memoryview(seed) - except TypeError as exception: - raise TypeError("seed must support the buffer protocol, such as `bytes` or `bytearray`") from exception + return mldsa_priv - seed = bytes(seed) + @classmethod + def make_key_from_seed(cls, mldsa_type: MlDsaType, seed: bytes) -> MlDsaPrivate: + """ + Deterministically generate the key from a seed. - if len(seed) != cls.ML_DSA_KEYGEN_SEED_LENGTH: - raise ValueError(f"Seed for generating ML-DSA key must be {cls.ML_DSA_KEYGEN_SEED_LENGTH} bytes") + :param mldsa_type: ML-DSA type + :type mldsa_type: MlDsaType + :param seed: the (32 byte) seed from which to deterministically create the key + :type seed: bytes + """ + mldsa_priv = cls(mldsa_type) - ret = _lib.wc_dilithium_make_key_from_seed(mldsa_priv.native_object, seed) + try: + memoryview(seed) + except TypeError as exception: + raise TypeError("seed must support the buffer protocol, such as `bytes` or `bytearray`") from exception - if ret < 0: # pragma: no cover - raise WolfCryptApiError("wc_dilithium_make_key_from_seed() error", ret) + seed = bytes(seed) - return mldsa_priv + if len(seed) != cls.ML_DSA_KEYGEN_SEED_LENGTH: + raise ValueError(f"Seed for generating ML-DSA key must be {cls.ML_DSA_KEYGEN_SEED_LENGTH} bytes") - @property - def pub_key_size(self) -> int: - """ - :return: public key size in bytes - :rtype: int - """ - return self._pub_key_size + ret = _lib.wc_dilithium_make_key_from_seed(mldsa_priv.native_object, seed) - @property - def priv_key_size(self) -> int: - """ - :return: private key size in bytes - :rtype: int - """ - size = _ffi.new("int *") - ret = _lib.wc_MlDsaKey_GetPrivLen(self.native_object, size) + if ret < 0: # pragma: no cover + raise WolfCryptApiError("wc_dilithium_make_key_from_seed() error", ret) - if ret < 0: # pragma: no cover - raise WolfCryptApiError("wc_MlDsaKey_GetPrivLen() error", ret) + return mldsa_priv - return size[0] - self.pub_key_size + if _lib.ML_DSA_PUBLIC_KEY_ENABLED: + @property + def pub_key_size(self) -> int: + """ + :return: public key size in bytes + :rtype: int + """ + return self._pub_key_size - def encode_pub_key(self) -> bytes: - """ - :return: exported public key - :rtype: bytes - """ - return self._encode_pub_key() + def encode_pub_key(self) -> bytes: + """ + :return: exported public key + :rtype: bytes + """ + return self._encode_pub_key() - def encode_priv_key(self) -> bytes: - """ - :return: exported private key - :rtype: bytes - """ - in_size = self.priv_key_size - priv_key = _ffi.new(f"byte[{in_size}]") - out_size = _ffi.new("word32 *") - out_size[0] = in_size - ret = _lib.wc_dilithium_export_private( - self.native_object, priv_key, out_size - ) + if _lib.ML_DSA_PRIVATE_KEY_ENABLED and _lib.ML_DSA_PUBLIC_KEY_ENABLED: + @property + def priv_key_size(self) -> int: + """ + :return: private key size in bytes + :rtype: int + """ + size = _ffi.new("int *") + ret = _lib.wc_MlDsaKey_GetPrivLen(self.native_object, size) - if ret < 0: # pragma: no cover - raise WolfCryptApiError("wc_dilithium_export_private() error", ret) + if ret < 0: # pragma: no cover + raise WolfCryptApiError("wc_MlDsaKey_GetPrivLen() error", ret) - if in_size != out_size[0]: - raise WolfCryptError(f"{in_size=} and {out_size[0]=} don't match") + return size[0] - self.pub_key_size - return _ffi.buffer(priv_key, out_size[0])[:] + def encode_priv_key(self) -> bytes: + """ + :return: exported private key + :rtype: bytes + """ + in_size = self.priv_key_size + priv_key = _ffi.new(f"byte[{in_size}]") + out_size = _ffi.new("word32 *") + out_size[0] = in_size + ret = _lib.wc_dilithium_export_private( + self.native_object, priv_key, out_size + ) - def decode_key(self, priv_key: BytesOrStr, pub_key: BytesOrStr | None = None) -> None: - """ - :param priv_key: private key to be imported - :type priv_key: bytes or str - :param pub_key: public key to be imported - :type pub_key: bytes or str or None - """ - priv_key_bytestype = t2b(priv_key) - ret = _lib.wc_dilithium_import_private( - priv_key_bytestype, - len(priv_key_bytestype), - self.native_object, - ) + if ret < 0: # pragma: no cover + raise WolfCryptApiError("wc_dilithium_export_private() error", ret) - if ret < 0: # pragma: no cover - raise WolfCryptApiError("wc_dilithium_import_private() error", ret) + if in_size != out_size[0]: + raise WolfCryptError(f"{in_size=} and {out_size[0]=} don't match") - if pub_key is not None: - self._decode_pub_key(pub_key) + return _ffi.buffer(priv_key, out_size[0])[:] - def sign(self, message: BytesOrStr, rng: Random | None = None, ctx: BytesOrStr | None = None) -> bytes: - """ - :param message: message to be signed - :type message: bytes or str - :param rng: random number generator for sign - :type rng: Random - :param ctx: context, maximum 255 bytes (optional by default but that requires support for no-context - signing/verification compiled in; pass empty string "" for FIPS-204 empty-context signing). - :type ctx: bytes or str. None for no-context signing. - :return: signature - :rtype: bytes - """ - if ctx is None and not _lib.ML_DSA_NO_CTX_ENABLED: - raise WolfCryptError("support for signing without context is disabled") + if _lib.ML_DSA_PRIVATE_KEY_ENABLED: + def decode_key(self, priv_key: BytesOrStr, pub_key: BytesOrStr | None = None) -> None: + """ + :param priv_key: private key to be imported + :type priv_key: bytes or str + :param pub_key: public key to be imported + :type pub_key: bytes or str or None + """ + if pub_key is not None and not _lib.ML_DSA_PUBLIC_KEY_ENABLED: + raise NotImplementedError("ML-DSA public key import is not supported by this wolfSSL build") - if rng is None: - rng = Random() - msg_bytestype = t2b(message) - in_size = self.sig_size - signature = _ffi.new(f"byte[{in_size}]") - out_size = _ffi.new("word32 *") - out_size[0] = in_size - - if ctx is not None: - ctx_bytestype = t2b(ctx) - if len(ctx_bytestype) > 255: - raise ValueError(f"context length {len(ctx_bytestype)} too large: must be 255 bytes or less") - ret = _lib.wc_dilithium_sign_ctx_msg( - ctx_bytestype, - len(ctx_bytestype), # length must be < 256 bytes - msg_bytestype, - len(msg_bytestype), - signature, - out_size, + priv_key_bytestype = t2b(priv_key) + ret = _lib.wc_dilithium_import_private( + priv_key_bytestype, + len(priv_key_bytestype), self.native_object, - rng.native_object, - ) - if ret < 0: # pragma: no cover - raise WolfCryptApiError("wc_dilithium_sign_ctx_msg() error", ret) - else: - ret = _lib.wc_dilithium_sign_msg( - msg_bytestype, - len(msg_bytestype), - signature, - out_size, - self.native_object, - rng.native_object, ) + if ret < 0: # pragma: no cover - raise WolfCryptApiError("wc_dilithium_sign_msg() error", ret) + raise WolfCryptApiError("wc_dilithium_import_private() error", ret) - if in_size != out_size[0]: - raise WolfCryptError(f"{in_size=} and {out_size[0]=} don't match") + if pub_key is not None: + self._decode_pub_key(pub_key) - return _ffi.buffer(signature, out_size[0])[:] + if _lib.ML_DSA_SIGN_ENABLED: + def sign(self, message: BytesOrStr, rng: Random | None = None, ctx: BytesOrStr | None = None) -> bytes: + """ + :param message: message to be signed + :type message: bytes or str + :param rng: random number generator for sign + :type rng: Random + :param ctx: context, maximum 255 bytes (optional by default but that requires support for no-context + signing/verification compiled in; pass empty string "" for FIPS-204 empty-context signing). + :type ctx: bytes or str. None for no-context signing. + :return: signature + :rtype: bytes + """ + if ctx is None and not _lib.ML_DSA_NO_CTX_ENABLED: + raise WolfCryptError("support for signing without context is disabled") - def sign_with_seed(self, message: BytesOrStr, seed: bytes, ctx: BytesOrStr | None = None) -> bytes: - """ - :param message: message to be signed - :type message: bytes or str - :param seed: 32-byte seed for deterministic signature generation. - :type seed: bytes - :param ctx: context, maximum 255 bytes (optional by default but that requires support for no-context - signing/verification compiled in; pass empty string "" for FIPS-204 empty-context signing). - :type ctx: bytes or str. None for no-context signing. - :return: signature - :rtype: bytes - """ - if ctx is None and not _lib.ML_DSA_NO_CTX_ENABLED: - raise WolfCryptError("support for signing without context is disabled") + if rng is None: + rng = Random() + msg_bytestype = t2b(message) + in_size = self.sig_size + signature = _ffi.new(f"byte[{in_size}]") + out_size = _ffi.new("word32 *") + out_size[0] = in_size + + if ctx is not None: + ctx_bytestype = t2b(ctx) + if len(ctx_bytestype) > 255: + raise ValueError(f"context length {len(ctx_bytestype)} too large: must be 255 bytes or less") + ret = _lib.wc_dilithium_sign_ctx_msg( + ctx_bytestype, + len(ctx_bytestype), # length must be < 256 bytes + msg_bytestype, + len(msg_bytestype), + signature, + out_size, + self.native_object, + rng.native_object, + ) + if ret < 0: # pragma: no cover + raise WolfCryptApiError("wc_dilithium_sign_ctx_msg() error", ret) + else: + ret = _lib.wc_dilithium_sign_msg( + msg_bytestype, + len(msg_bytestype), + signature, + out_size, + self.native_object, + rng.native_object, + ) + if ret < 0: # pragma: no cover + raise WolfCryptApiError("wc_dilithium_sign_msg() error", ret) - msg_bytestype = t2b(message) - in_size = self.sig_size - signature = _ffi.new(f"byte[{in_size}]") - out_size = _ffi.new("word32 *") - out_size[0] = in_size + if in_size != out_size[0]: + raise WolfCryptError(f"{in_size=} and {out_size[0]=} don't match") - try: - memoryview(seed) - except TypeError as exception: - raise TypeError("seed must support the buffer protocol, such as `bytes` or `bytearray`") from exception + return _ffi.buffer(signature, out_size[0])[:] - seed = bytes(seed) + def sign_with_seed(self, message: BytesOrStr, seed: bytes, ctx: BytesOrStr | None = None) -> bytes: + """ + :param message: message to be signed + :type message: bytes or str + :param seed: 32-byte seed for deterministic signature generation. + :type seed: bytes + :param ctx: context, maximum 255 bytes (optional by default but that requires support for no-context + signing/verification compiled in; pass empty string "" for FIPS-204 empty-context signing). + :type ctx: bytes or str. None for no-context signing. + :return: signature + :rtype: bytes + """ + if ctx is None and not _lib.ML_DSA_NO_CTX_ENABLED: + raise WolfCryptError("support for signing without context is disabled") - if len(seed) != ML_DSA_SIGNATURE_SEED_LENGTH: - raise ValueError(f"Seed for generating a signature must be {ML_DSA_SIGNATURE_SEED_LENGTH} bytes.") + msg_bytestype = t2b(message) + in_size = self.sig_size + signature = _ffi.new(f"byte[{in_size}]") + out_size = _ffi.new("word32 *") + out_size[0] = in_size - if ctx is not None: - ctx_bytestype = t2b(ctx) - if len(ctx_bytestype) > 255: - raise ValueError( - f"context length {len(ctx_bytestype)} too large: must be 255 or less" + try: + memoryview(seed) + except TypeError as exception: + raise TypeError("seed must support the buffer protocol, such as `bytes` or `bytearray`") from exception + + seed = bytes(seed) + + if len(seed) != ML_DSA_SIGNATURE_SEED_LENGTH: + raise ValueError(f"Seed for generating a signature must be {ML_DSA_SIGNATURE_SEED_LENGTH} bytes.") + + if ctx is not None: + ctx_bytestype = t2b(ctx) + if len(ctx_bytestype) > 255: + raise ValueError( + f"context length {len(ctx_bytestype)} too large: must be 255 or less" + ) + ret = _lib.wc_dilithium_sign_ctx_msg_with_seed( + ctx_bytestype, + len(ctx_bytestype), # length must be < 256 bytes + msg_bytestype, + len(msg_bytestype), + signature, + out_size, + self.native_object, + seed, ) - ret = _lib.wc_dilithium_sign_ctx_msg_with_seed( - ctx_bytestype, - len(ctx_bytestype), # length must be < 256 bytes - msg_bytestype, - len(msg_bytestype), - signature, - out_size, - self.native_object, - seed, - ) - if ret < 0: # pragma: no cover - raise WolfCryptApiError("wc_dilithium_sign_ctx_msg_with_seed() error", ret) - else: - ret = _lib.wc_dilithium_sign_msg_with_seed( - msg_bytestype, - len(msg_bytestype), - signature, - out_size, - self.native_object, - seed, - ) - if ret < 0: # pragma: no cover - raise WolfCryptApiError("wc_dilithium_sign_msg_with_seed() error", ret) + if ret < 0: # pragma: no cover + raise WolfCryptApiError("wc_dilithium_sign_ctx_msg_with_seed() error", ret) + else: + ret = _lib.wc_dilithium_sign_msg_with_seed( + msg_bytestype, + len(msg_bytestype), + signature, + out_size, + self.native_object, + seed, + ) + if ret < 0: # pragma: no cover + raise WolfCryptApiError("wc_dilithium_sign_msg_with_seed() error", ret) - if in_size != out_size[0]: - raise WolfCryptError(f"{in_size=} and {out_size[0]=} don't match") + if in_size != out_size[0]: + raise WolfCryptError(f"{in_size=} and {out_size[0]=} don't match") - return _ffi.buffer(signature, out_size[0])[:] + return _ffi.buffer(signature, out_size[0])[:] class MlDsaPublic(_MlDsaBase): - @property - def key_size(self) -> int: - """ - :return: public key size in bytes - :rtype: int - """ - return self._pub_key_size + if _lib.ML_DSA_PUBLIC_KEY_ENABLED: + @property + def key_size(self) -> int: + """ + :return: public key size in bytes + :rtype: int + """ + return self._pub_key_size - def decode_key(self, pub_key: BytesOrStr) -> None: - """ - :param pub_key: public key to be imported - :type pub_key: bytes or str - """ - self._decode_pub_key(pub_key) + def decode_key(self, pub_key: BytesOrStr) -> None: + """ + :param pub_key: public key to be imported + :type pub_key: bytes or str + """ + self._decode_pub_key(pub_key) - def encode_key(self) -> bytes: - """ - :return: exported public key - :rtype: bytes - """ - return self._encode_pub_key() + def encode_key(self) -> bytes: + """ + :return: exported public key + :rtype: bytes + """ + return self._encode_pub_key() diff --git a/wolfcrypt/hashes.py b/wolfcrypt/hashes.py index 76180af..875aaa4 100644 --- a/wolfcrypt/hashes.py +++ b/wolfcrypt/hashes.py @@ -32,6 +32,8 @@ from wolfcrypt.utils import t2b, b2h, BytesOrStr if TYPE_CHECKING: + from collections.abc import Callable + from _cffi_backend import FFI @@ -323,19 +325,22 @@ class Sha3(_Sha): SHA3_384_DIGEST_SIZE = 48 SHA3_512_DIGEST_SIZE = 64 - _SHA3_FREE = { - 28: _lib.wc_Sha3_224_Free, - 32: _lib.wc_Sha3_256_Free, - 48: _lib.wc_Sha3_384_Free, - 64: _lib.wc_Sha3_512_Free, - } - - _SHA3_COPY = { - 28: _lib.wc_Sha3_224_Copy, - 32: _lib.wc_Sha3_256_Copy, - 48: _lib.wc_Sha3_384_Copy, - 64: _lib.wc_Sha3_512_Copy, - } + _SHA3_FREE: dict[int, Callable[[FFI.CData], None]] = {} + _SHA3_COPY: dict[int, Callable[[FFI.CData, FFI.CData], int]] = {} + if _lib.SHA3_224_ENABLED: + _SHA3_FREE[28] = _lib.wc_Sha3_224_Free + _SHA3_COPY[28] = _lib.wc_Sha3_224_Copy + if _lib.SHA3_256_ENABLED: + _SHA3_FREE[32] = _lib.wc_Sha3_256_Free + _SHA3_COPY[32] = _lib.wc_Sha3_256_Copy + if _lib.SHA3_384_ENABLED: + _SHA3_FREE[48] = _lib.wc_Sha3_384_Free + _SHA3_COPY[48] = _lib.wc_Sha3_384_Copy + if _lib.SHA3_512_ENABLED: + _SHA3_FREE[64] = _lib.wc_Sha3_512_Free + _SHA3_COPY[64] = _lib.wc_Sha3_512_Copy + _SHA3_FLAGS = {28: "SHA3_224_ENABLED", 32: "SHA3_256_ENABLED", + 48: "SHA3_384_ENABLED", 64: "SHA3_512_ENABLED"} def __del__(self) -> None: # Unlike the SHA-1/2 classes, Sha3's _delete is set per-instance @@ -350,6 +355,9 @@ def __del__(self) -> None: self._delete(self._native_object) def __init__(self, string: BytesOrStr | None = None, size: int = SHA3_384_DIGEST_SIZE) -> None: # pylint: disable=W0231 + flag = self._SHA3_FLAGS.get(size) + if flag is not None and not getattr(_lib, flag): + raise NotImplementedError(f"SHA3-{size * 8} is not supported by this wolfSSL build") self._native_object = _ffi.new(self._native_type) self._shallow_copy = False self.digest_size = size @@ -393,37 +401,37 @@ def copy(self) -> Sha3: @override def _init(self) -> int: - if self.digest_size == Sha3.SHA3_224_DIGEST_SIZE: + if self.digest_size == Sha3.SHA3_224_DIGEST_SIZE and _lib.SHA3_224_ENABLED: return _lib.wc_InitSha3_224(self._native_object, _ffi.NULL, 0) - if self.digest_size == Sha3.SHA3_256_DIGEST_SIZE: + if self.digest_size == Sha3.SHA3_256_DIGEST_SIZE and _lib.SHA3_256_ENABLED: return _lib.wc_InitSha3_256(self._native_object, _ffi.NULL, 0) - if self.digest_size == Sha3.SHA3_384_DIGEST_SIZE: + if self.digest_size == Sha3.SHA3_384_DIGEST_SIZE and _lib.SHA3_384_ENABLED: return _lib.wc_InitSha3_384(self._native_object, _ffi.NULL, 0) - if self.digest_size == Sha3.SHA3_512_DIGEST_SIZE: + if self.digest_size == Sha3.SHA3_512_DIGEST_SIZE and _lib.SHA3_512_ENABLED: return _lib.wc_InitSha3_512(self._native_object, _ffi.NULL, 0) return -1 @override def _update(self, data: bytes) -> int: - if self.digest_size == Sha3.SHA3_224_DIGEST_SIZE: + if self.digest_size == Sha3.SHA3_224_DIGEST_SIZE and _lib.SHA3_224_ENABLED: return _lib.wc_Sha3_224_Update(self._native_object, data, len(data)) - if self.digest_size == Sha3.SHA3_256_DIGEST_SIZE: + if self.digest_size == Sha3.SHA3_256_DIGEST_SIZE and _lib.SHA3_256_ENABLED: return _lib.wc_Sha3_256_Update(self._native_object, data, len(data)) - if self.digest_size == Sha3.SHA3_384_DIGEST_SIZE: + if self.digest_size == Sha3.SHA3_384_DIGEST_SIZE and _lib.SHA3_384_ENABLED: return _lib.wc_Sha3_384_Update(self._native_object, data, len(data)) - if self.digest_size == Sha3.SHA3_512_DIGEST_SIZE: + if self.digest_size == Sha3.SHA3_512_DIGEST_SIZE and _lib.SHA3_512_ENABLED: return _lib.wc_Sha3_512_Update(self._native_object, data, len(data)) return -1 @override def _final(self, obj: FFI.CData, ret: FFI.CData) -> int: - if self.digest_size == Sha3.SHA3_224_DIGEST_SIZE: + if self.digest_size == Sha3.SHA3_224_DIGEST_SIZE and _lib.SHA3_224_ENABLED: return _lib.wc_Sha3_224_Final(obj, ret) - if self.digest_size == Sha3.SHA3_256_DIGEST_SIZE: + if self.digest_size == Sha3.SHA3_256_DIGEST_SIZE and _lib.SHA3_256_ENABLED: return _lib.wc_Sha3_256_Final(obj, ret) - if self.digest_size == Sha3.SHA3_384_DIGEST_SIZE: + if self.digest_size == Sha3.SHA3_384_DIGEST_SIZE and _lib.SHA3_384_ENABLED: return _lib.wc_Sha3_384_Final(obj, ret) - if self.digest_size == Sha3.SHA3_512_DIGEST_SIZE: + if self.digest_size == Sha3.SHA3_512_DIGEST_SIZE and _lib.SHA3_512_ENABLED: return _lib.wc_Sha3_512_Final(obj, ret) return -1 diff --git a/wolfcrypt/pwdbased.py b/wolfcrypt/pwdbased.py index e679923..70cc302 100644 --- a/wolfcrypt/pwdbased.py +++ b/wolfcrypt/pwdbased.py @@ -27,7 +27,7 @@ from wolfcrypt.exceptions import WolfCryptApiError -if _lib.PWDBASED_ENABLED: +if _lib.PBKDF2_ENABLED: def PBKDF2(password: bytes | str, salt: bytes | str, iterations: int, key_length: int, hash_type: int) -> bytes: if isinstance(salt, str): salt = str.encode(salt) diff --git a/wolfcrypt/random.py b/wolfcrypt/random.py index c29844c..1a5a336 100644 --- a/wolfcrypt/random.py +++ b/wolfcrypt/random.py @@ -35,6 +35,8 @@ class Random: def __init__(self, nonce: __builtins__.bytes = b"", device_id: int = -2) -> None: self._native_object: _lib.RNG | None = None + if not _lib.RNG_ENABLED: + raise NotImplementedError("RNG is not supported by this wolfSSL build") self._native_object = _ffi.new("WC_RNG *") ret = _lib.wc_InitRngNonce_ex(self._native_object, nonce, len(nonce), _ffi.NULL, device_id) @@ -42,8 +44,9 @@ def __init__(self, nonce: __builtins__.bytes = b"", device_id: int = -2) -> None self._native_object = None raise WolfCryptApiError("RNG init error", ret) - # making sure _lib.wc_FreeRng outlives WC_RNG instances - _delete = staticmethod(_lib.wc_FreeRng) + if _lib.RNG_ENABLED: + # making sure _lib.wc_FreeRng outlives WC_RNG instances + _delete = staticmethod(_lib.wc_FreeRng) def __del__(self) -> None: if self._native_object is not None: