From 981adfe30e1dad26aa21ff33b34242e72817d328 Mon Sep 17 00:00:00 2001 From: Tobias Frauenschlaeger Date: Sun, 23 Aug 2026 07:16:16 +0000 Subject: [PATCH 1/3] Thread the default devId through every remaining algorithm wolfPSA_SetDefaultDevID() only reached the algorithms whose call sites already used a devId-aware initializer. Everything else went through the plain wc_*Init() form and ignored the configured value, so RSA, ECC, Ed25519, Ed448, X25519, the RNG, CMAC, HKDF, PBKDF2 and the SHA-1/SHA-2 families always ran locally. Move those call sites to the _ex forms. The plain forms are not uniform, which the getter now accounts for. Most pin INVALID_DEVID, but the SHA-2 family calls wc_CryptoCb_DefaultDevID(), and wc_ecc_init and wc_InitCmac select the CAAM device on QNX and iMXRT builds. Passing wolfPSA's own default unconditionally would have taken offload away from those builds, so an unconfigured wolfPSA defers to the same wolfCrypt selection. A configured devId wins over it, INVALID_DEVID included: passing that explicitly is how an application keeps every operation on the host. Several algorithms stay local. PSA_ALG_DETERMINISTIC_ECDSA promises RFC 6979 and the callback contract carries no deterministic flag, so an offloaded sign would quietly return a randomized signature. The RSA key whose only job is to decode a DER blob keeps INVALID_DEVID, because wc_RsaEncryptSize() answers a hard-coded 2048 bits for a device that declines the call, which would let a zero-modulus key past the size check. AES key wrap keeps the keyless wc_AesKeyWrap() wrappers: wc_AesKeyWrap_ex() falls back to software when the device has no keywrap handler, and a device that installed the KEK through WOLF_CRYPTO_CB_AES_SETKEY leaves no software key schedule for that fallback to run on. X448, RIPEMD-160, MD5, Ascon and ChaCha20-Poly1305 stay local because wolfCrypt either exposes no devId for them or ignores the one it accepts. Three paths that only a device can reach are now handled. The HMAC path of psa_mac_* never called wc_HmacInit() at all, so it ran with devId 0 from its zeroed allocation, and a callback registered on device 0 captured wolfPSA's HMACs while every other algorithm stayed local. ECC key generation read the private scalar straight out of the key struct, so a backend that keeps the scalar produced an all-zero private key with PSA_SUCCESS; it checks the key type first, the way wolfCrypt's own exporters do, and reports HARDWARE_FAILURE. The Ed25519 and X25519 generate paths call those exporters instead of bypassing them, so no zero key was ever emitted there, but the refusal arrived as BAD_FUNC_ARG and ECC_BAD_ARG_E, which both map to PSA_ERROR_INVALID_ARGUMENT and blame the caller for a fault that was the device's; they check the private-scalar flag directly so all three report the same status. The CMAC init-failure paths free the context, because the callback branch can return an error after attaching per-context state. On the build side, cryptocb.c was missing from the wolfCrypt source list, so no WOLF_CRYPTO_CB build could link, and the Makefile had no header dependency tracking, which let a header edit leave every object stale. psa_devid_cryptocb_test covers four states: nothing registered leaves the default at INVALID_DEVID, a registered device with no explicit setting must catch every algorithm family through wolfCrypt's own selection, an explicit devId must override that selection while the other device stays registered, and an explicit INVALID_DEVID must stop dispatching entirely. Its probe device sits on a non-zero devId so an algorithm skipping its wc_*Init() goes missing from the counts rather than blending in. It links its own WOLF_CRYPTO_CB build of the library from a directory scoped to the flag set, so an ASAN lane never reuses uninstrumented objects, and it stays out of the default target because that second build is not free. A muted-device case per curve covers the private-scalar guards: the callback reports a successful keygen without filling in the key, and removing any one guard turns the suite red rather than leaving it green. --- .github/workflows/test-psa-api.yml | 4 +- .gitignore | 3 + CHANGELOG.md | 18 + Makefile | 13 +- src/psa_asymmetric_api.c | 4 +- src/psa_ecc.c | 46 +- src/psa_ed25519_ed448.c | 40 +- src/psa_engine.c | 19 +- src/psa_hash_engine.c | 21 +- src/psa_key_derivation.c | 82 ++- src/psa_key_storage.c | 3 + src/psa_mac.c | 12 +- src/psa_mldsa.c | 4 +- src/psa_mlkem.c | 2 +- src/psa_montgomery.c | 24 +- src/psa_random.c | 2 +- src/psa_rsa.c | 20 +- test/Makefile | 39 +- test/README.md | 9 + test/psa_server/psa_devid_cryptocb_test.c | 777 ++++++++++++++++++++++ wolfpsa/psa_engine.h | 43 +- 21 files changed, 1084 insertions(+), 101 deletions(-) create mode 100644 test/psa_server/psa_devid_cryptocb_test.c diff --git a/.github/workflows/test-psa-api.yml b/.github/workflows/test-psa-api.yml index 15cf56d..4d4e1c2 100644 --- a/.github/workflows/test-psa-api.yml +++ b/.github/workflows/test-psa-api.yml @@ -120,6 +120,7 @@ jobs: psa_cipher_oneshot_len_test psa_pqc_export_seed_test psa_key_declared_bits_test + psa_devid_cryptocb_test - name: Run PSA API tests env: @@ -161,7 +162,8 @@ jobs: psa_key_infer_bits_test \ psa_cipher_oneshot_len_test \ psa_pqc_export_seed_test \ - psa_key_declared_bits_test; do + psa_key_declared_bits_test \ + psa_devid_cryptocb_test; do echo "=== $t ===" rm -rf test/.store ./test/$t diff --git a/.gitignore b/.gitignore index 6ba85ef..dfbff7d 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ CLAUDE.md # Real-hardware board configs are kept local, not in-tree (CI is native_sim only) zephyr/**/boards/nucleo_*.conf zephyr/**/boards/nucleo_*.overlay +/test/psa_devid_cryptocb_test +/test/build-cryptocb +/test/build-cryptocb-asan diff --git a/CHANGELOG.md b/CHANGELOG.md index d993a0c..6648297 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,24 @@ wolfSSL master. - `psa_purge_key()`: confirms a key exists and reports its status (wolfPSA keeps no in-RAM cache of persistent key material, so there is nothing to evict). +- Crypto callback offload: `wolfPSA_SetDefaultDevID()` now reaches every + algorithm whose wolfCrypt initializer accepts a devId, adding RSA, ECC, + Ed25519, Ed448, X25519, AES-KW, CMAC, HKDF, PBKDF2, the RNG and the + SHA-1/SHA-2 families to the coverage. X448, RIPEMD-160, MD5, Ascon and + ChaCha20-Poly1305 stay local, either because wolfCrypt exposes no devId + for them or because it ignores the one it accepts. Leaving the default at + INVALID_DEVID now defers to `wc_CryptoCb_DefaultDevID()`, so a build that + relied on wolfCrypt selecting a device by itself (CAAM, `WC_USE_DEVID`, or + the first registered device) keeps that behaviour; calling + `wolfPSA_SetDefaultDevID(INVALID_DEVID)` explicitly forces local execution + instead. PSA_ALG_DETERMINISTIC_ECDSA always signs locally, because the + crypto callback contract cannot express the RFC 6979 requirement. + `cryptocb.c` is part of the build so a library can be compiled with + `WOLF_CRYPTO_CB`. +- Fixed: the HMAC path of `psa_mac_*` never called `wc_HmacInit()`, so the + operation ran with devId 0 instead of the configured default. A crypto + callback registered on device 0 captured wolfPSA's HMACs while every + other algorithm stayed local. - Optional thread-safe key store: with `WOLFPSA_THREAD_SAFE` a single mutex built on wolfCrypt's portable `wc_*Mutex` API (created in `psa_crypto_init()`) guards the volatile-key list and id counter for concurrent PSA callers; a diff --git a/Makefile b/Makefile index 67bf70c..d6e53bd 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,7 @@ WOLFCRYPT_SRC := \ $(WOLFSSL_PATH)/wolfcrypt/src/cmac.c \ $(WOLFSSL_PATH)/wolfcrypt/src/coding.c \ $(WOLFSSL_PATH)/wolfcrypt/src/cpuid.c \ + $(WOLFSSL_PATH)/wolfcrypt/src/cryptocb.c \ $(WOLFSSL_PATH)/wolfcrypt/src/curve25519.c \ $(WOLFSSL_PATH)/wolfcrypt/src/curve448.c \ $(WOLFSSL_PATH)/wolfcrypt/src/des3.c \ @@ -86,6 +87,7 @@ ifneq ($(strip $(PSA_INCLUDE)),) CPPFLAGS += -I$(PSA_INCLUDE) endif +DEPFLAGS := -MMD -MP CFLAGS ?= -O2 WARNFLAGS ?= -Wall -Wextra -Werror CFLAGS += $(WARNFLAGS) @@ -119,19 +121,22 @@ $(SHLIBNAME): $(OBJ_PIC) $(WOLFCRYPT_OBJ_PIC) $(EXPORT_MAP) $(OBJDIR)/%.o: src/%.c @mkdir -p $(OBJDIR) - $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ + $(CC) $(CPPFLAGS) $(DEPFLAGS) $(CFLAGS) -c $< -o $@ $(OBJDIR)/wolfcrypt_%.o: $(WOLFSSL_PATH)/wolfcrypt/src/%.c @mkdir -p $(OBJDIR) - $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ + $(CC) $(CPPFLAGS) $(DEPFLAGS) $(CFLAGS) -c $< -o $@ $(OBJDIR_PIC)/%.o: src/%.c @mkdir -p $(OBJDIR_PIC) - $(CC) $(CPPFLAGS) $(CFLAGS) -fPIC -c $< -o $@ + $(CC) $(CPPFLAGS) $(DEPFLAGS) $(CFLAGS) -fPIC -c $< -o $@ $(OBJDIR_PIC)/wolfcrypt_%.o: $(WOLFSSL_PATH)/wolfcrypt/src/%.c @mkdir -p $(OBJDIR_PIC) - $(CC) $(CPPFLAGS) $(CFLAGS) -fPIC -c $< -o $@ + $(CC) $(CPPFLAGS) $(DEPFLAGS) $(CFLAGS) -fPIC -c $< -o $@ clean: rm -rf $(BUILD_DIR) $(LIBNAME) $(SHLIBNAME) + +-include $(OBJ:.o=.d) $(WOLFCRYPT_OBJ:.o=.d) +-include $(OBJ_PIC:.o=.d) $(WOLFCRYPT_OBJ_PIC:.o=.d) diff --git a/src/psa_asymmetric_api.c b/src/psa_asymmetric_api.c index f142d4c..0523c03 100644 --- a/src/psa_asymmetric_api.c +++ b/src/psa_asymmetric_api.c @@ -1405,12 +1405,12 @@ psa_status_t wolfpsa_key_agreement_secret(psa_algorithm_t alg, } } - ret = wc_ecc_init(&priv); + ret = wc_ecc_init_ex(&priv, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { wolfpsa_forcezero_free_key_data(key_data, key_data_length); return wc_error_to_psa_status(ret); } - ret = wc_ecc_init(&pub); + ret = wc_ecc_init_ex(&pub, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { wc_ecc_free(&priv); wolfpsa_forcezero_free_key_data(key_data, key_data_length); diff --git a/src/psa_ecc.c b/src/psa_ecc.c index fff8758..c0006c1 100644 --- a/src/psa_ecc.c +++ b/src/psa_ecc.c @@ -65,6 +65,7 @@ psa_status_t psa_asymmetric_sign_ecc(psa_key_type_t key_type, size_t raw_sig_len; byte* der_sig = NULL; byte* rs = NULL; + int devId; /* Check if key type is ECC key pair */ if (!PSA_KEY_TYPE_IS_ECC_KEY_PAIR(key_type)) { @@ -89,14 +90,32 @@ psa_status_t psa_asymmetric_sign_ecc(psa_key_type_t key_type, return PSA_ERROR_INVALID_ARGUMENT; } + /* wc_ecc_set_deterministic_ex() only steers the software signer, and the + * crypto callback contract carries no deterministic flag, so an offloaded + * sign would silently return a randomized signature. Keep the RFC 6979 + * guarantee PSA_ALG_DETERMINISTIC_ECDSA makes and stay local. */ + devId = wolfPSA_GetDefaultDevID(); + if (PSA_ALG_IS_DETERMINISTIC_ECDSA(alg)) { +#ifdef WOLF_CRYPTO_CB_ONLY_ECC + /* No software signer to stay local on, so the guarantee cannot be + * met at all. Say so rather than fail later as NO_VALID_DEVID. */ + return PSA_ERROR_NOT_SUPPORTED; +#else + devId = INVALID_DEVID; +#endif + } + /* Initialize ECC key */ - ret = wc_ecc_init(&ecc); + ret = wc_ecc_init_ex(&ecc, NULL, devId); if (ret != 0) { return wc_error_to_psa_status(ret); } - /* Initialize RNG */ - ret = wc_InitRng(&rng); + /* Initialize RNG. The same devId as the signer: k is already derived by + * the time any signer runs, so this RNG only blinds, but a deterministic + * sign should not depend on a device RNG the pin above just opted out + * of. */ + ret = wc_InitRng_ex(&rng, NULL, devId); if (ret != 0) { wc_ecc_free(&ecc); return wc_error_to_psa_status(ret); @@ -238,7 +257,7 @@ psa_status_t psa_asymmetric_verify_ecc(psa_key_type_t key_type, } /* Initialize ECC key */ - ret = wc_ecc_init(&ecc); + ret = wc_ecc_init_ex(&ecc, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } @@ -336,13 +355,13 @@ psa_status_t psa_asymmetric_generate_key_ecc(psa_key_type_t key_type, } /* Initialize ECC key */ - ret = wc_ecc_init(&ecc); + ret = wc_ecc_init_ex(&ecc, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } /* Initialize RNG */ - ret = wc_InitRng(&rng); + ret = wc_InitRng_ex(&rng, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { wc_ecc_free(&ecc); return wc_error_to_psa_status(ret); @@ -369,6 +388,19 @@ psa_status_t psa_asymmetric_generate_key_ecc(psa_key_type_t key_type, wc_ecc_free(&ecc); return PSA_ERROR_BUFFER_TOO_SMALL; } + /* An offload device may keep the scalar and leave ecc.k at zero, which + * mp_to_unsigned_bin_len() would happily export as an all-zero private + * key. wolfCrypt's own exporters reject that, so check the same way. + * wc_ecc_make_key_ex() has already succeeded here, so a device holds a + * key this path cannot reclaim: wc_ecc_free() releases the software + * struct only, and wolfPSA has no opaque-key model to destroy the device + * copy through. An integrator whose backend keeps the scalar has to free + * the backend slot itself. */ + if (ecc.type != ECC_PRIVATEKEY) { + wc_FreeRng(&rng); + wc_ecc_free(&ecc); + return PSA_ERROR_HARDWARE_FAILURE; + } ret = mp_to_unsigned_bin_len(ecc.k, private_key, priv_len); if (ret != MP_OKAY) { wc_FreeRng(&rng); @@ -424,7 +456,7 @@ psa_status_t psa_asymmetric_export_public_key_ecc(psa_key_type_t key_type, } /* Initialize ECC key */ - ret = wc_ecc_init(&ecc); + ret = wc_ecc_init_ex(&ecc, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } diff --git a/src/psa_ed25519_ed448.c b/src/psa_ed25519_ed448.c index 2a15926..35af200 100644 --- a/src/psa_ed25519_ed448.c +++ b/src/psa_ed25519_ed448.c @@ -104,7 +104,7 @@ psa_status_t psa_asymmetric_sign_ed25519(psa_key_type_t key_type, ctx_len = (byte)context_length; /* Initialize ED25519 key */ - ret = wc_ed25519_init(&ed_key); + ret = wc_ed25519_init_ex(&ed_key, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } @@ -205,7 +205,7 @@ psa_status_t psa_asymmetric_verify_ed25519(psa_key_type_t key_type, ctx_len = (byte)context_length; /* Initialize ED25519 key */ - ret = wc_ed25519_init(&ed_key); + ret = wc_ed25519_init_ex(&ed_key, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } @@ -283,13 +283,13 @@ psa_status_t psa_asymmetric_generate_key_ed25519(psa_key_type_t key_type, } /* Initialize ED25519 key */ - ret = wc_ed25519_init(&ed_key); + ret = wc_ed25519_init_ex(&ed_key, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } /* Initialize RNG */ - ret = wc_InitRng(&rng); + ret = wc_InitRng_ex(&rng, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { wc_ed25519_free(&ed_key); return wc_error_to_psa_status(ret); @@ -303,6 +303,19 @@ psa_status_t psa_asymmetric_generate_key_ed25519(psa_key_type_t key_type, return wc_error_to_psa_status(ret); } + /* An offload device may report success while keeping the scalar, which + * leaves privKeySet clear. wc_ed25519_export_private_only() refuses that, + * but only as BAD_FUNC_ARG, which maps to PSA_ERROR_INVALID_ARGUMENT and + * blames the caller for a device fault. Report it the way the ECC path + * does. The device still holds a key this path cannot reclaim, so an + * integrator whose backend keeps the scalar has to free the backend slot + * itself. */ + if (!ed_key.privKeySet) { + wc_FreeRng(&rng); + wc_ed25519_free(&ed_key); + return PSA_ERROR_HARDWARE_FAILURE; + } + /* Export private key */ priv_len32 = (word32)private_key_size; ret = wc_ed25519_export_private_only(&ed_key, private_key, &priv_len32); @@ -355,7 +368,7 @@ psa_status_t psa_asymmetric_export_public_key_ed25519(psa_key_type_t key_type, } /* Initialize ED25519 key */ - ret = wc_ed25519_init(&ed_key); + ret = wc_ed25519_init_ex(&ed_key, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } @@ -447,7 +460,7 @@ psa_status_t psa_asymmetric_sign_ed448(psa_key_type_t key_type, ctx_len = (byte)context_length; /* Initialize ED448 key */ - ret = wc_ed448_init(&ed_key); + ret = wc_ed448_init_ex(&ed_key, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } @@ -541,7 +554,7 @@ psa_status_t psa_asymmetric_verify_ed448(psa_key_type_t key_type, ctx_len = (byte)context_length; /* Initialize ED448 key */ - ret = wc_ed448_init(&ed_key); + ret = wc_ed448_init_ex(&ed_key, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } @@ -615,13 +628,13 @@ psa_status_t psa_asymmetric_generate_key_ed448(psa_key_type_t key_type, } /* Initialize ED448 key */ - ret = wc_ed448_init(&ed_key); + ret = wc_ed448_init_ex(&ed_key, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } /* Initialize RNG */ - ret = wc_InitRng(&rng); + ret = wc_InitRng_ex(&rng, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { wc_ed448_free(&ed_key); return wc_error_to_psa_status(ret); @@ -635,6 +648,13 @@ psa_status_t psa_asymmetric_generate_key_ed448(psa_key_type_t key_type, return wc_error_to_psa_status(ret); } + /* Deliberately no privKeySet check here, unlike the Ed25519 path above. + * ed448.c dispatches Sign and Verify only, so wc_ed448_make_key() has no + * crypto callback to offload to and always sets privKeySet on success. + * Passing a devId to wc_ed448_init_ex() does not change that: an + * initializer that accepts a devId is not the same as a dispatch. Add the + * check if wolfCrypt ever gains a WC_PK_TYPE_ED448_KEYGEN. */ + /* Export private key */ priv_len32 = (word32)private_key_size; ret = wc_ed448_export_private_only(&ed_key, private_key, &priv_len32); @@ -687,7 +707,7 @@ psa_status_t psa_asymmetric_export_public_key_ed448(psa_key_type_t key_type, } /* Initialize ED448 key */ - ret = wc_ed448_init(&ed_key); + ret = wc_ed448_init_ex(&ed_key, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } diff --git a/src/psa_engine.c b/src/psa_engine.c index cb6fbee..0e6713f 100644 --- a/src/psa_engine.c +++ b/src/psa_engine.c @@ -34,18 +34,33 @@ #include /* Runtime-settable devId threaded through every wolfPSA-internal - * wc_*Init()/wc_NewRsaKey() call. INVALID_DEVID (the default) keeps - * the original behaviour: wolfCrypt runs the operation locally. */ + * wc_*Init()/wc_NewRsaKey() call. INVALID_DEVID means no wolfPSA-specific + * choice has been made, which defers to wolfCrypt's own device selection. */ static int wolfPSA_default_devid = INVALID_DEVID; +/* Distinguishes "never configured", which defers to wolfCrypt, from an + * explicit INVALID_DEVID, which is how a caller forces local execution. */ +static int wolfPSA_devid_configured = 0; + int wolfPSA_SetDefaultDevID(int devId) { wolfPSA_default_devid = devId; + wolfPSA_devid_configured = 1; return 0; } int wolfPSA_GetDefaultDevID(void) { +#ifdef WOLF_CRYPTO_CB + /* Several wolfCrypt initializers (the SHA-2 family, and wc_ecc_init or + * wc_InitCmac on CAAM targets) pick a device themselves rather than + * defaulting to INVALID_DEVID. Deferring to the same selection keeps a + * caller that never sets a wolfPSA devId on the behaviour it had before + * wolfPSA started passing one. */ + if (!wolfPSA_devid_configured) { + return wc_CryptoCb_DefaultDevID(); + } +#endif return wolfPSA_default_devid; } diff --git a/src/psa_hash_engine.c b/src/psa_hash_engine.c index 7ca8709..4a2f937 100644 --- a/src/psa_hash_engine.c +++ b/src/psa_hash_engine.c @@ -400,36 +400,43 @@ psa_status_t psa_hash_setup(psa_hash_operation_t *operation, #endif #ifndef NO_SHA case PSA_ALG_SHA_1: - ret = wc_InitSha(&ctx->ctx.sha1); + ret = wc_InitSha_ex(&ctx->ctx.sha1, NULL, + wolfPSA_GetDefaultDevID()); break; #endif #ifndef NO_SHA256 case PSA_ALG_SHA_256: - ret = wc_InitSha256(&ctx->ctx.sha256); + ret = wc_InitSha256_ex(&ctx->ctx.sha256, NULL, + wolfPSA_GetDefaultDevID()); break; #endif #ifdef WOLFSSL_SHA224 case PSA_ALG_SHA_224: - ret = wc_InitSha224(&ctx->ctx.sha224); + ret = wc_InitSha224_ex(&ctx->ctx.sha224, NULL, + wolfPSA_GetDefaultDevID()); break; #endif #ifdef WOLFSSL_SHA384 case PSA_ALG_SHA_384: - ret = wc_InitSha384(&ctx->ctx.sha384); + ret = wc_InitSha384_ex(&ctx->ctx.sha384, NULL, + wolfPSA_GetDefaultDevID()); break; #endif #ifdef WOLFSSL_SHA512 case PSA_ALG_SHA_512: - ret = wc_InitSha512(&ctx->ctx.sha512); + ret = wc_InitSha512_ex(&ctx->ctx.sha512, NULL, + wolfPSA_GetDefaultDevID()); break; #if !defined(WOLFSSL_NOSHA512_224) case PSA_ALG_SHA_512_224: - ret = wc_InitSha512_224(&ctx->ctx.sha512); + ret = wc_InitSha512_224_ex(&ctx->ctx.sha512, NULL, + wolfPSA_GetDefaultDevID()); break; #endif #if !defined(WOLFSSL_NOSHA512_256) case PSA_ALG_SHA_512_256: - ret = wc_InitSha512_256(&ctx->ctx.sha512); + ret = wc_InitSha512_256_ex(&ctx->ctx.sha512, NULL, + wolfPSA_GetDefaultDevID()); break; #endif #endif diff --git a/src/psa_key_derivation.c b/src/psa_key_derivation.c index 79d99d1..a4c5062 100644 --- a/src/psa_key_derivation.c +++ b/src/psa_key_derivation.c @@ -892,20 +892,22 @@ static psa_status_t wolfpsa_kdf_hkdf(wolfpsa_kdf_ctx_t *ctx, } if (output_length < (size_t)hash_len) { uint8_t tmp[WC_MAX_DIGEST_SIZE]; - ret = wc_HKDF_Extract(hash_type, - ctx->salt, (word32)ctx->salt_length, - ctx->secret, (word32)ctx->secret_length, - tmp); + ret = wc_HKDF_Extract_ex(hash_type, + ctx->salt, (word32)ctx->salt_length, + ctx->secret, (word32)ctx->secret_length, + tmp, NULL, + wolfPSA_GetDefaultDevID()); if (ret == 0) { XMEMCPY(output, tmp, output_length); } wc_ForceZero(tmp, sizeof(tmp)); return ret == 0 ? PSA_SUCCESS : wc_error_to_psa_status(ret); } - ret = wc_HKDF_Extract(hash_type, - ctx->salt, (word32)ctx->salt_length, - ctx->secret, (word32)ctx->secret_length, - prk); + ret = wc_HKDF_Extract_ex(hash_type, + ctx->salt, (word32)ctx->salt_length, + ctx->secret, (word32)ctx->secret_length, + prk, NULL, + wolfPSA_GetDefaultDevID()); if (ret != 0) { wc_ForceZero(prk, (size_t)hash_len); return wc_error_to_psa_status(ret); @@ -922,10 +924,11 @@ static psa_status_t wolfpsa_kdf_hkdf(wolfpsa_kdf_ctx_t *ctx, (wolfpsa_check_word32_length(output_length) != PSA_SUCCESS)) { return PSA_ERROR_INVALID_ARGUMENT; } - ret = wc_HKDF_Expand(hash_type, - ctx->secret, (word32)ctx->secret_length, - ctx->info, (word32)ctx->info_length, - output, (word32)output_length); + ret = wc_HKDF_Expand_ex(hash_type, + ctx->secret, (word32)ctx->secret_length, + ctx->info, (word32)ctx->info_length, + output, (word32)output_length, NULL, + wolfPSA_GetDefaultDevID()); return ret == 0 ? PSA_SUCCESS : wc_error_to_psa_status(ret); } @@ -943,20 +946,22 @@ static psa_status_t wolfpsa_kdf_hkdf(wolfpsa_kdf_ctx_t *ctx, (wolfpsa_check_word32_length(output_length) != PSA_SUCCESS)) { return PSA_ERROR_INVALID_ARGUMENT; } - ret = wc_HKDF_Extract(hash_type, - ctx->salt, (word32)ctx->salt_length, - ctx->secret, (word32)ctx->secret_length, - prk); + ret = wc_HKDF_Extract_ex(hash_type, + ctx->salt, (word32)ctx->salt_length, + ctx->secret, (word32)ctx->secret_length, + prk, NULL, + wolfPSA_GetDefaultDevID()); if (ret != 0) { status = wc_error_to_psa_status(ret); wc_ForceZero(prk, sizeof(prk)); return status; } - ret = wc_HKDF_Expand(hash_type, - prk, (word32)hash_len, - ctx->info, (word32)ctx->info_length, - output, (word32)output_length); + ret = wc_HKDF_Expand_ex(hash_type, + prk, (word32)hash_len, + ctx->info, (word32)ctx->info_length, + output, (word32)output_length, NULL, + wolfPSA_GetDefaultDevID()); if (ret != 0) { status = wc_error_to_psa_status(ret); wc_ForceZero(prk, sizeof(prk)); @@ -1102,9 +1107,10 @@ static psa_status_t wolfpsa_kdf_pbkdf2(wolfpsa_kdf_ctx_t *ctx, output_length > (size_t)INT_MAX) { return PSA_ERROR_INVALID_ARGUMENT; } - ret = wc_PBKDF2(output, password, (int)ctx->password_length, - salt, (int)ctx->salt_length, - (int)ctx->cost, (int)output_length, hash_type); + ret = wc_PBKDF2_ex(output, password, (int)ctx->password_length, + salt, (int)ctx->salt_length, + (int)ctx->cost, (int)output_length, hash_type, + NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } @@ -1141,9 +1147,11 @@ static psa_status_t wolfpsa_kdf_pbkdf2(wolfpsa_kdf_ctx_t *ctx, } else { XMEMSET(zero_key, 0, sizeof(zero_key)); - ret = wc_InitCmac(&cmac, zero_key, (word32)sizeof(zero_key), - WC_CMAC_AES, NULL); + ret = wc_InitCmac_ex(&cmac, zero_key, (word32)sizeof(zero_key), + WC_CMAC_AES, NULL, NULL, + wolfPSA_GetDefaultDevID()); if (ret != 0) { + wc_CmacFree(&cmac); status = wc_error_to_psa_status(ret); goto cleanup; } @@ -1182,9 +1190,11 @@ static psa_status_t wolfpsa_kdf_pbkdf2(wolfpsa_kdf_ctx_t *ctx, block_input[ctx->salt_length + 2] = (uint8_t)((i >> 8) & 0xff); block_input[ctx->salt_length + 3] = (uint8_t)(i & 0xff); - ret = wc_InitCmac(&cmac, prf_key, (word32)sizeof(prf_key), - WC_CMAC_AES, NULL); + ret = wc_InitCmac_ex(&cmac, prf_key, (word32)sizeof(prf_key), + WC_CMAC_AES, NULL, NULL, + wolfPSA_GetDefaultDevID()); if (ret != 0) { + wc_CmacFree(&cmac); status = wc_error_to_psa_status(ret); goto cleanup; } @@ -1202,9 +1212,11 @@ static psa_status_t wolfpsa_kdf_pbkdf2(wolfpsa_kdf_ctx_t *ctx, XMEMCPY(t_block, u_block, WC_AES_BLOCK_SIZE); for (j = 1; j < ctx->cost; j++) { - ret = wc_InitCmac(&cmac, prf_key, (word32)sizeof(prf_key), - WC_CMAC_AES, NULL); + ret = wc_InitCmac_ex(&cmac, prf_key, (word32)sizeof(prf_key), + WC_CMAC_AES, NULL, NULL, + wolfPSA_GetDefaultDevID()); if (ret != 0) { + wc_CmacFree(&cmac); status = wc_error_to_psa_status(ret); goto cleanup; } @@ -1482,9 +1494,11 @@ static psa_status_t wolfpsa_kdf_sp800_108_cmac(wolfpsa_kdf_ctx_t *ctx, L_buf[3] = (uint8_t)( L_bits_lo & 0xff); /* --- compute K_0 = CMAC(K_IN, Label || 0x00 || Context || [L]_4) --- */ - ret = wc_InitCmac(&cmac, ctx->secret, (word32)ctx->secret_length, - WC_CMAC_AES, NULL); + ret = wc_InitCmac_ex(&cmac, ctx->secret, (word32)ctx->secret_length, + WC_CMAC_AES, NULL, NULL, + wolfPSA_GetDefaultDevID()); if (ret != 0) { + wc_CmacFree(&cmac); status = wc_error_to_psa_status(ret); goto cmac_cleanup; } @@ -1520,9 +1534,11 @@ static psa_status_t wolfpsa_kdf_sp800_108_cmac(wolfpsa_kdf_ctx_t *ctx, counter_buf[2] = (uint8_t)((counter >> 8) & 0xff); counter_buf[3] = (uint8_t)( counter & 0xff); - ret = wc_InitCmac(&cmac, ctx->secret, (word32)ctx->secret_length, - WC_CMAC_AES, NULL); + ret = wc_InitCmac_ex(&cmac, ctx->secret, (word32)ctx->secret_length, + WC_CMAC_AES, NULL, NULL, + wolfPSA_GetDefaultDevID()); if (ret != 0) { + wc_CmacFree(&cmac); status = wc_error_to_psa_status(ret); goto cmac_cleanup; } diff --git a/src/psa_key_storage.c b/src/psa_key_storage.c index f80f9df..c784dc7 100644 --- a/src/psa_key_storage.c +++ b/src/psa_key_storage.c @@ -528,6 +528,9 @@ static psa_status_t wolfpsa_infer_key_bits(psa_key_attributes_t* attr, int ret; int size; + /* Parse-only key: no devId, because wc_RsaEncryptSize() answers a + * hard-coded 2048 bits for a device that declines the call, which + * would let a zero-modulus key past the size check below. */ ret = wc_InitRsaKey(&rsa, NULL); if (ret != 0) { return psa_wc_error_to_psa_status(ret); diff --git a/src/psa_mac.c b/src/psa_mac.c index 91b84ec..0dc4def 100644 --- a/src/psa_mac.c +++ b/src/psa_mac.c @@ -294,15 +294,19 @@ static psa_status_t wolfpsa_mac_setup(psa_mac_operation_t *operation, XFREE(ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); return PSA_ERROR_NOT_SUPPORTED; } - ret = wc_HmacSetKey(&ctx->ctx.hmac, hash_type, key_data, - (word32)key_data_length); + ret = wc_HmacInit(&ctx->ctx.hmac, NULL, wolfPSA_GetDefaultDevID()); + if (ret == 0) { + ret = wc_HmacSetKey(&ctx->ctx.hmac, hash_type, key_data, + (word32)key_data_length); + } ctx->type = WOLFPSA_MAC_HMAC; } #ifdef WOLFSSL_CMAC else if (PSA_ALG_IS_BLOCK_CIPHER_MAC(alg) && PSA_ALG_FULL_LENGTH_MAC(alg) == PSA_ALG_CMAC) { - ret = wc_InitCmac(&ctx->ctx.cmac, key_data, (word32)key_data_length, - WC_CMAC_AES, NULL); + ret = wc_InitCmac_ex(&ctx->ctx.cmac, key_data, + (word32)key_data_length, WC_CMAC_AES, NULL, NULL, + wolfPSA_GetDefaultDevID()); ctx->type = WOLFPSA_MAC_CMAC; } #endif diff --git a/src/psa_mldsa.c b/src/psa_mldsa.c index aff31d0..a51acf3 100644 --- a/src/psa_mldsa.c +++ b/src/psa_mldsa.c @@ -305,7 +305,7 @@ psa_status_t wolfpsa_mldsa_sign(size_t bits, const uint8_t *key_data, /* Signing over the raw message. */ if (alg == PSA_ALG_ML_DSA) { /* Hedged: requires an RNG. */ - ret = wc_InitRng(&rng); + ret = wc_InitRng_ex(&rng, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { wc_MlDsaKey_Free(&key); return wc_error_to_psa_status(ret); @@ -351,7 +351,7 @@ psa_status_t wolfpsa_mldsa_sign(size_t bits, const uint8_t *key_data, wc_MlDsaKey_Free(&key); return PSA_ERROR_NOT_SUPPORTED; } - ret = wc_InitRng(&rng); + ret = wc_InitRng_ex(&rng, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { wc_MlDsaKey_Free(&key); return wc_error_to_psa_status(ret); diff --git a/src/psa_mlkem.c b/src/psa_mlkem.c index f6516d0..39eb0f9 100644 --- a/src/psa_mlkem.c +++ b/src/psa_mlkem.c @@ -230,7 +230,7 @@ psa_status_t wolfpsa_mlkem_encapsulate(size_t bits, psa_key_type_t key_type, return PSA_ERROR_BUFFER_TOO_SMALL; } - ret = wc_InitRng(&rng); + ret = wc_InitRng_ex(&rng, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { wc_MlKemKey_Free(&key); return wc_error_to_psa_status(ret); diff --git a/src/psa_montgomery.c b/src/psa_montgomery.c index 08e0641..39edcce 100644 --- a/src/psa_montgomery.c +++ b/src/psa_montgomery.c @@ -76,17 +76,29 @@ psa_status_t psa_asymmetric_generate_key_x25519(psa_key_type_t key_type, priv_len = (word32)private_key_size; pub_len = (word32)public_key_size; - ret = wc_curve25519_init(&key); + ret = wc_curve25519_init_ex(&key, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } - ret = wc_InitRng(&rng); + ret = wc_InitRng_ex(&rng, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { wc_curve25519_free(&key); return wc_error_to_psa_status(ret); } ret = wc_curve25519_make_key(&rng, CURVE25519_KEYSIZE, &key); + /* An offload device may report success while keeping the scalar, which + * leaves privSet clear. wc_curve25519_export_private_raw_ex() refuses + * that, but only as ECC_BAD_ARG_E, which maps to + * PSA_ERROR_INVALID_ARGUMENT and blames the caller for a device fault. + * Report it the way the ECC path does. The device still holds a key this + * path cannot reclaim, so an integrator whose backend keeps the scalar + * has to free the backend slot itself. */ + if ((ret == 0) && (!key.privSet)) { + wc_FreeRng(&rng); + wc_curve25519_free(&key); + return PSA_ERROR_HARDWARE_FAILURE; + } if (ret == 0) { ret = wc_curve25519_export_private_raw_ex(&key, private_key, &priv_len, @@ -196,17 +208,17 @@ psa_status_t psa_asymmetric_key_agreement_x25519( } out_len = (word32)output_size; - ret = wc_curve25519_init(&priv); + ret = wc_curve25519_init_ex(&priv, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } - ret = wc_curve25519_init(&pub); + ret = wc_curve25519_init_ex(&pub, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { wc_curve25519_free(&priv); return wc_error_to_psa_status(ret); } #ifdef WOLFSSL_CURVE25519_BLINDING - ret = wc_InitRng(&rng); + ret = wc_InitRng_ex(&rng, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { wc_curve25519_free(&pub); wc_curve25519_free(&priv); @@ -287,7 +299,7 @@ psa_status_t psa_asymmetric_generate_key_x448(psa_key_type_t key_type, if (ret != 0) { return wc_error_to_psa_status(ret); } - ret = wc_InitRng(&rng); + ret = wc_InitRng_ex(&rng, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { wc_curve448_free(&key); return wc_error_to_psa_status(ret); diff --git a/src/psa_random.c b/src/psa_random.c index f87c648..7e89c48 100644 --- a/src/psa_random.c +++ b/src/psa_random.c @@ -61,7 +61,7 @@ psa_status_t psa_generate_random(uint8_t *output, size_t output_size) wolfpsa_trace("psa_generate_random(%zu)", output_size); /* Initialize the RNG */ - ret = wc_InitRng(&rng); + ret = wc_InitRng_ex(&rng, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } diff --git a/src/psa_rsa.c b/src/psa_rsa.c index bfa8837..6651b54 100644 --- a/src/psa_rsa.c +++ b/src/psa_rsa.c @@ -155,7 +155,7 @@ psa_status_t psa_asymmetric_sign_rsa(psa_key_type_t key_type, } /* Initialize RSA key */ - ret = wc_InitRsaKey(&rsa_key, NULL); + ret = wc_InitRsaKey_ex(&rsa_key, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } @@ -168,7 +168,7 @@ psa_status_t psa_asymmetric_sign_rsa(psa_key_type_t key_type, } /* Initialize RNG */ - ret = wc_InitRng(&rng); + ret = wc_InitRng_ex(&rng, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { wc_FreeRsaKey(&rsa_key); return wc_error_to_psa_status(ret); @@ -296,7 +296,7 @@ psa_status_t psa_asymmetric_verify_rsa(psa_key_type_t key_type, } /* Initialize RSA key */ - ret = wc_InitRsaKey(&rsa_key, NULL); + ret = wc_InitRsaKey_ex(&rsa_key, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } @@ -500,7 +500,7 @@ psa_status_t psa_asymmetric_encrypt_rsa(psa_key_type_t key_type, } /* Initialize RSA key */ - ret = wc_InitRsaKey(&rsa_key, NULL); + ret = wc_InitRsaKey_ex(&rsa_key, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } @@ -520,7 +520,7 @@ psa_status_t psa_asymmetric_encrypt_rsa(psa_key_type_t key_type, } /* Initialize RNG */ - ret = wc_InitRng(&rng); + ret = wc_InitRng_ex(&rng, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { wc_FreeRsaKey(&rsa_key); return wc_error_to_psa_status(ret); @@ -610,7 +610,7 @@ psa_status_t psa_asymmetric_decrypt_rsa(psa_key_type_t key_type, } /* Initialize RSA key */ - ret = wc_InitRsaKey(&rsa_key, NULL); + ret = wc_InitRsaKey_ex(&rsa_key, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } @@ -625,7 +625,7 @@ psa_status_t psa_asymmetric_decrypt_rsa(psa_key_type_t key_type, #ifdef WC_RSA_BLINDING /* RSA blinding requires an RNG associated with the key. Unlike the signing * routines, wc_RsaPrivateDecrypt() takes no RNG argument, so set it here. */ - ret = wc_InitRng(&rng); + ret = wc_InitRng_ex(&rng, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { wc_FreeRsaKey(&rsa_key); return wc_error_to_psa_status(ret); @@ -722,13 +722,13 @@ psa_status_t psa_asymmetric_generate_key_rsa(psa_key_type_t key_type, } /* Initialize RSA key */ - ret = wc_InitRsaKey(&rsa_key, NULL); + ret = wc_InitRsaKey_ex(&rsa_key, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } /* Initialize RNG */ - ret = wc_InitRng(&rng); + ret = wc_InitRng_ex(&rng, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { wc_FreeRsaKey(&rsa_key); return wc_error_to_psa_status(ret); @@ -801,7 +801,7 @@ psa_status_t psa_asymmetric_export_public_key_rsa(psa_key_type_t key_type, } /* Initialize RSA key */ - ret = wc_InitRsaKey(&rsa_key, NULL); + ret = wc_InitRsaKey_ex(&rsa_key, NULL, wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } diff --git a/test/Makefile b/test/Makefile index 809436d..00ec540 100644 --- a/test/Makefile +++ b/test/Makefile @@ -120,7 +120,7 @@ PSA_ECC_CURVE_ID_TEST_DEPS = $(WOLFPSA_PATH)/$(BUILD_DIR)/obj/psa_asymmetric.o \ $(WOLFPSA_PATH)/$(BUILD_DIR)/obj/psa_pq.o PSA_RSA_PSS_TEST_OBJS = psa_server/psa_rsa_pss_interop_test.o -.PHONY: all clean rebuild-wolfssl-psa require-wolfssl-lib +.PHONY: all clean rebuild-wolfssl-psa require-wolfssl-lib FORCE all: info $(BINARIES) @@ -174,6 +174,36 @@ psa_tls_client: make -C psa_server/tls_client cp psa_server/tls_client/psa_tls_client ./psa_tls_client +# devId crypto-callback coverage. wolfCrypt compiles its callback dispatch out +# unless WOLF_CRYPTO_CB is defined, and this test reads wolfCrypt structures +# directly, so it links against a dedicated library built from the repository +# user_settings.h with that knob enabled instead of the shared libwolfpsa. +CRYPTOCB_BUILD_DIR := $(abspath .)/build-cryptocb$(if $(SANITIZE_FLAGS),-asan) +CRYPTOCB_LIB := $(CRYPTOCB_BUILD_DIR)/libwolfpsa-cryptocb.a +CRYPTOCB_CPPFLAGS := -DWOLFSSL_USER_SETTINGS -DWOLF_CRYPTO_CB \ + -I$(abspath $(WOLFSSL_PATH)) -I$(WOLFPSA_ABS) -I$(WOLFPSA_ABS)/wolfpsa \ + -I$(WOLFPSA_ABS)/src + +# FORCE, because only the sub-make knows whether a library source changed. +# Without it make treats the existing archive as permanently up to date and +# the test relinks against a stale copy of the code it is meant to guard. +$(CRYPTOCB_LIB): FORCE + $(MAKE) -C $(WOLFPSA_PATH) $@ \ + LIBNAME=$@ \ + BUILD_DIR=$(CRYPTOCB_BUILD_DIR) \ + WOLFSSL_PATH=$(abspath $(WOLFSSL_PATH)) \ + WOLFSSL_CPPFLAGS="-DWOLFSSL_USER_SETTINGS -DWOLF_CRYPTO_CB" \ + CFLAGS="$(CFLAGS)" \ + LDFLAGS="$(SANITIZE_FLAGS)" + +FORCE: + +# FORCE here too: the archive is scoped per sanitizer setting but this binary +# is not, so without it an ASAN build left behind stays "up to date". +psa_devid_cryptocb_test: psa_server/psa_devid_cryptocb_test.c $(CRYPTOCB_LIB) FORCE + $(CC) $(CRYPTOCB_CPPFLAGS) $(CFLAGS) -o $@ $< $(CRYPTOCB_LIB) \ + $(SANITIZE_FLAGS) -lpthread -lm + wolfcrypt-psa-benchmark: make -C wolfcrypt-benchmark cp wolfcrypt-benchmark/wolfcrypt-psa-benchmark ./wolfcrypt-psa-benchmark @@ -198,10 +228,10 @@ rebuild-wolfssl-psa: $(WOLFPSA_PATH)/libwolfpsa.so --with-psa-include="$(WOLFPSA_ABS)/wolfpsa" \ --with-psa-lib="$(WOLFPSA_ABS)" \ --with-psa-lib-name=wolfpsa \ - CFLAGS="$(DEBUG_FLAGS) $(SANITIZE_FLAGS)" \ + CFLAGS="-O2 $(DEBUG_FLAGS) $(SANITIZE_FLAGS)" \ CPPFLAGS="-DWOLFSSL_PSA_NO_RNG -DWOLFSSL_PSA_NO_HASH -DWOLFSSL_PSA_NO_AES" \ LDFLAGS="$(SANITIZE_FLAGS) -Wl,-rpath,$(WOLFPSA_ABS) -Wl,-rpath-link,$(WOLFPSA_ABS)" - $(MAKE) -C "$(WOLFSSL_PATH)" CFLAGS="$(DEBUG_FLAGS) $(SANITIZE_FLAGS)" LDFLAGS="$(SANITIZE_FLAGS) -Wl,-rpath,$(WOLFPSA_ABS) -Wl,-rpath-link,$(WOLFPSA_ABS)" src/libwolfssl.la + $(MAKE) -C "$(WOLFSSL_PATH)" CFLAGS="-O2 $(DEBUG_FLAGS) $(SANITIZE_FLAGS)" LDFLAGS="$(SANITIZE_FLAGS) -Wl,-rpath,$(WOLFPSA_ABS) -Wl,-rpath-link,$(WOLFPSA_ABS)" src/libwolfssl.la %.o: %.c $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ @@ -210,4 +240,5 @@ clean: make -C psa_server/tls_client clean make -C wolfcrypt-benchmark clean rm -f psa_server/*.o - rm -f $(BINARIES) *.o *.log + rm -rf $(abspath .)/build-cryptocb $(abspath .)/build-cryptocb-asan + rm -f $(BINARIES) psa_devid_cryptocb_test *.o *.log diff --git a/test/README.md b/test/README.md index a3a41fe..bdf3a1a 100644 --- a/test/README.md +++ b/test/README.md @@ -13,6 +13,15 @@ This directory contains standalone tests and demos for wolfPSA integration. PSA-backed code paths via `WOLFSSL_HAVE_PSA`. - Source: `wolfcrypt-benchmark/main.c` (build uses local wolfCrypt sources). +- `psa_devid_cryptocb_test` + - Checks that every algorithm family reaches a registered crypto callback, + both through wolfCrypt's own device selection and through an explicit + `wolfPSA_SetDefaultDevID()`, and that an explicit `INVALID_DEVID` keeps + every operation local. + - Links against its own `WOLF_CRYPTO_CB` build of the library, so it needs no + prebuilt wolfSSL. + - Source: `psa_server/psa_devid_cryptocb_test.c` + - `psa_tls_server` + `psa_tls_client` - TLS server built with wolfPSA, and a static client built from wolfSSL sources (`psa_server/tls_client`). diff --git a/test/psa_server/psa_devid_cryptocb_test.c b/test/psa_server/psa_devid_cryptocb_test.c new file mode 100644 index 0000000..9bc0c4d --- /dev/null +++ b/test/psa_server/psa_devid_cryptocb_test.c @@ -0,0 +1,777 @@ +/* psa_devid_cryptocb_test.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfPSA. + * + * wolfPSA 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. + * + * wolfPSA 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 + */ + +/* + * Checks that every algorithm family reaches a registered crypto callback, + * both through wolfCrypt's own device selection and through an explicit + * wolfPSA_SetDefaultDevID(). + * + * The probe device deliberately sits on a non-zero devId: an algorithm that + * skipped its wc_*Init() call carries devId 0 from its zeroed context and + * would go missing from the counts. + * + * Unlike the other tests here this one inspects wolfCrypt structures, so it + * must build against the same user_settings.h as the library itself. + */ + +#ifndef WOLFSSL_USER_SETTINGS +#define WOLFSSL_USER_SETTINGS +#endif + +#include +#include +#include + +#include +#include + +#include +#include + +/* Arbitrary devId standing in for an offload backend. */ +#define TEST_DEVID 0x505341 + +/* Registered first, so wolfCrypt's own selection picks it when wolfPSA has + * no explicit devId. Non-zero on purpose, see the note above. */ +#define PROBE_DEVID 5 + +/* When set to a WC_PK_TYPE_* keygen value, the callback claims that key + * generation succeeded without putting a scalar in the key. That is the + * backend that keeps the private key on the device, which psa_generate_key() + * has to refuse rather than export as an all-zero scalar. One value rather + * than a flag per curve, so a test can only ever mute the one keygen it is + * checking. */ +static int mute_keygen_pk_type = WC_PK_TYPE_NONE; + +static int seen[WC_ALGO_TYPE_MAX + 1]; +static int seen_total; +static int seen_wrong_devid; +static int expect_devid = INVALID_DEVID; + +static const char *algo_name(int algo_type) +{ + switch (algo_type) { + case WC_ALGO_TYPE_HASH: return "hash"; + case WC_ALGO_TYPE_CIPHER: return "cipher"; + case WC_ALGO_TYPE_PK: return "pk"; + case WC_ALGO_TYPE_RNG: return "rng"; + case WC_ALGO_TYPE_SEED: return "seed"; + case WC_ALGO_TYPE_HMAC: return "hmac"; + case WC_ALGO_TYPE_CMAC: return "cmac"; + default: return "other"; + } +} + +/* Records the dispatch and declines it, so wolfCrypt falls back to software + * and the PSA results stay correct. */ +static int count_cb(int devId, wc_CryptoInfo *info, void *ctx) +{ + (void)ctx; + + if (devId != expect_devid) { + seen_wrong_devid++; + } + else if (info != NULL && info->algo_type >= 0 && + info->algo_type <= WC_ALGO_TYPE_MAX) { + seen[info->algo_type]++; + } + seen_total++; + + if (mute_keygen_pk_type != WC_PK_TYPE_NONE && info != NULL && + info->algo_type == WC_ALGO_TYPE_PK && + info->pk.type == mute_keygen_pk_type) { + /* Success, but the key is left without a private scalar. */ + return 0; + } + + return CRYPTOCB_UNAVAILABLE; +} + +static void reset_counts(int devId) +{ + XMEMSET(seen, 0, sizeof(seen)); + seen_total = 0; + seen_wrong_devid = 0; + expect_devid = devId; +} + +static int fail(const char *label, psa_status_t status) +{ + printf("FAIL %s status=%d\n", label, (int)status); + return 1; +} + +static int exercise_random(void) +{ + uint8_t out[16]; + psa_status_t st; + + st = psa_generate_random(out, sizeof(out)); + if (st != PSA_SUCCESS) + return fail("psa_generate_random", st); + return 0; +} + +static int exercise_hash(void) +{ + static const uint8_t msg[] = "wolfPSA devId coverage"; + uint8_t digest[PSA_HASH_MAX_SIZE]; + size_t digest_len = 0; + psa_status_t st; + + st = psa_hash_compute(PSA_ALG_SHA_256, msg, sizeof(msg) - 1, + digest, sizeof(digest), &digest_len); + if (st != PSA_SUCCESS) + return fail("psa_hash_compute", st); + return 0; +} + +static int exercise_mac(psa_algorithm_t alg, psa_key_type_t type, + size_t key_len, const char *label) +{ + static const uint8_t msg[] = "wolfPSA devId coverage"; + uint8_t key_bytes[32]; + uint8_t mac[PSA_MAC_MAX_SIZE]; + size_t mac_len = 0; + psa_key_attributes_t attr = psa_key_attributes_init(); + psa_key_id_t key = PSA_KEY_ID_NULL; + psa_status_t st; + + XMEMSET(key_bytes, 0x5a, sizeof(key_bytes)); + + psa_set_key_type(&attr, type); + psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_SIGN_MESSAGE); + psa_set_key_algorithm(&attr, alg); + + st = psa_import_key(&attr, key_bytes, key_len, &key); + if (st != PSA_SUCCESS) + return fail(label, st); + + st = psa_mac_compute(key, alg, msg, sizeof(msg) - 1, + mac, sizeof(mac), &mac_len); + psa_destroy_key(key); + if (st != PSA_SUCCESS) + return fail(label, st); + return 0; +} + +static int exercise_cipher(void) +{ + static const uint8_t plain[32] = { 0 }; + uint8_t key_bytes[16]; + uint8_t out[64]; + size_t out_len = 0; + psa_key_attributes_t attr = psa_key_attributes_init(); + psa_key_id_t key = PSA_KEY_ID_NULL; + psa_status_t st; + + XMEMSET(key_bytes, 0x2b, sizeof(key_bytes)); + + psa_set_key_type(&attr, PSA_KEY_TYPE_AES); + psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_ENCRYPT); + psa_set_key_algorithm(&attr, PSA_ALG_CBC_NO_PADDING); + + st = psa_import_key(&attr, key_bytes, sizeof(key_bytes), &key); + if (st != PSA_SUCCESS) + return fail("aes import", st); + + st = psa_cipher_encrypt(key, PSA_ALG_CBC_NO_PADDING, plain, sizeof(plain), + out, sizeof(out), &out_len); + psa_destroy_key(key); + if (st != PSA_SUCCESS) + return fail("psa_cipher_encrypt", st); + return 0; +} + +static int exercise_ecdsa(void) +{ + uint8_t digest[32]; + uint8_t sig[PSA_SIGNATURE_MAX_SIZE]; + size_t sig_len = 0; + psa_key_attributes_t attr = psa_key_attributes_init(); + psa_key_id_t key = PSA_KEY_ID_NULL; + psa_status_t st; + + XMEMSET(digest, 0x11, sizeof(digest)); + + psa_set_key_type(&attr, + PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); + psa_set_key_bits(&attr, 256); + psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_SIGN_HASH); + psa_set_key_algorithm(&attr, PSA_ALG_ECDSA(PSA_ALG_SHA_256)); + + st = psa_generate_key(&attr, &key); + if (st != PSA_SUCCESS) + return fail("ecc generate", st); + + st = psa_sign_hash(key, PSA_ALG_ECDSA(PSA_ALG_SHA_256), + digest, sizeof(digest), sig, sizeof(sig), &sig_len); + psa_destroy_key(key); + if (st != PSA_SUCCESS) + return fail("psa_sign_hash ecdsa", st); + return 0; +} + +#ifdef HAVE_ED25519 +static int exercise_eddsa(void) +{ + static const uint8_t msg[] = "wolfPSA devId coverage"; + uint8_t sig[PSA_SIGNATURE_MAX_SIZE]; + size_t sig_len = 0; + psa_key_attributes_t attr = psa_key_attributes_init(); + psa_key_id_t key = PSA_KEY_ID_NULL; + psa_status_t st; + + psa_set_key_type(&attr, + PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS)); + psa_set_key_bits(&attr, 255); + psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_SIGN_MESSAGE); + psa_set_key_algorithm(&attr, PSA_ALG_PURE_EDDSA); + + st = psa_generate_key(&attr, &key); + if (st != PSA_SUCCESS) + return fail("ed25519 generate", st); + + st = psa_sign_message(key, PSA_ALG_PURE_EDDSA, msg, sizeof(msg) - 1, + sig, sizeof(sig), &sig_len); + psa_destroy_key(key); + if (st != PSA_SUCCESS) + return fail("psa_sign_message ed25519", st); + return 0; +} +#endif /* HAVE_ED25519 */ + +#if !defined(NO_AES) && defined(HAVE_AES_KEYWRAP) +/* RFC 3394 section 4.1 vector, so the software fallback after a declined + * dispatch is checked against a known answer rather than just a status. */ +static const uint8_t kKekRfc3394[16] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F +}; + +static const uint8_t kPlainRfc3394[16] = { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF +}; + +static const uint8_t kCipherRfc3394[24] = { + 0x1F, 0xA6, 0x8B, 0x0A, 0x81, 0x12, 0xB4, 0x47, + 0xAE, 0xF3, 0x4B, 0xD8, 0xFB, 0x5A, 0x7B, 0x82, + 0x9D, 0x3E, 0x86, 0x23, 0x71, 0xD2, 0xCF, 0xE5 +}; + +static int exercise_keywrap(void) +{ + uint8_t wrapped[32]; + size_t wrapped_len = 0; + psa_key_attributes_t kattr = psa_key_attributes_init(); + psa_key_attributes_t tattr = psa_key_attributes_init(); + psa_key_id_t kek = PSA_KEY_ID_NULL; + psa_key_id_t target = PSA_KEY_ID_NULL; + psa_status_t st; + int ret = 0; + + psa_set_key_type(&kattr, PSA_KEY_TYPE_AES); + psa_set_key_usage_flags(&kattr, PSA_KEY_USAGE_WRAP); + psa_set_key_algorithm(&kattr, PSA_ALG_KW); + + st = psa_import_key(&kattr, kKekRfc3394, sizeof(kKekRfc3394), &kek); + if (st != PSA_SUCCESS) + return fail("kek import", st); + + psa_set_key_type(&tattr, PSA_KEY_TYPE_AES); + psa_set_key_usage_flags(&tattr, PSA_KEY_USAGE_EXPORT); + psa_set_key_algorithm(&tattr, PSA_ALG_KW); + + st = psa_import_key(&tattr, kPlainRfc3394, sizeof(kPlainRfc3394), &target); + if (st != PSA_SUCCESS) { + psa_destroy_key(kek); + return fail("wrap target import", st); + } + + st = psa_wrap_key(kek, PSA_ALG_KW, target, + wrapped, sizeof(wrapped), &wrapped_len); + if (st != PSA_SUCCESS) { + ret = fail("psa_wrap_key", st); + } + else if (wrapped_len != sizeof(kCipherRfc3394) || + XMEMCMP(wrapped, kCipherRfc3394, sizeof(kCipherRfc3394)) != 0) { + printf("FAIL psa_wrap_key produced the wrong ciphertext\n"); + ret = 1; + } + + psa_destroy_key(target); + psa_destroy_key(kek); + return ret; +} +#endif /* !NO_AES && HAVE_AES_KEYWRAP */ + +#ifndef NO_RSA +static int exercise_rsa(void) +{ + uint8_t digest[32]; + uint8_t sig[PSA_SIGNATURE_MAX_SIZE]; + size_t sig_len = 0; + psa_key_attributes_t attr = psa_key_attributes_init(); + psa_key_id_t key = PSA_KEY_ID_NULL; + psa_status_t st; + + XMEMSET(digest, 0x22, sizeof(digest)); + + psa_set_key_type(&attr, PSA_KEY_TYPE_RSA_KEY_PAIR); + psa_set_key_bits(&attr, 2048); + psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_SIGN_HASH); + psa_set_key_algorithm(&attr, PSA_ALG_RSA_PKCS1V15_SIGN(PSA_ALG_SHA_256)); + + st = psa_generate_key(&attr, &key); + if (st != PSA_SUCCESS) + return fail("rsa generate", st); + + st = psa_sign_hash(key, PSA_ALG_RSA_PKCS1V15_SIGN(PSA_ALG_SHA_256), + digest, sizeof(digest), sig, sizeof(sig), &sig_len); + psa_destroy_key(key); + if (st != PSA_SUCCESS) + return fail("psa_sign_hash rsa", st); + return 0; +} +#endif /* NO_RSA */ + +#define KDF_MODE_HKDF 0 +#define KDF_MODE_EXTRACT 1 +#define KDF_MODE_EXPAND 2 +#define KDF_MODE_PBKDF2 3 +#define KDF_MODE_SP800 4 + +/* One derivation, driven to output so every input is consumed. Returns 0 on + * success, 1 on failure, and -1 when the build does not support alg. */ +static int run_derivation(psa_algorithm_t alg, int mode, size_t out_len, + const char *label) +{ + static const uint8_t secret[] = "wolfPSA devId kdf secret"; + static const uint8_t salt[] = "wolfPSA devId kdf salt"; + static const uint8_t info[] = "wolfPSA devId kdf info"; + static const uint8_t aes_key[16] = { + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c + }; + psa_key_derivation_operation_t op = psa_key_derivation_operation_init(); + uint8_t prk[32]; + uint8_t out[32]; + psa_status_t st; + + XMEMSET(prk, 0x5a, sizeof(prk)); + + st = psa_key_derivation_setup(&op, alg); + if (st == PSA_ERROR_NOT_SUPPORTED) + return -1; + if (st != PSA_SUCCESS) + return fail(label, st); + + if (mode == KDF_MODE_PBKDF2) { + st = psa_key_derivation_input_integer(&op, + PSA_KEY_DERIVATION_INPUT_COST, 2u); + if (st == PSA_SUCCESS) { + st = psa_key_derivation_input_bytes(&op, + PSA_KEY_DERIVATION_INPUT_SALT, salt, sizeof(salt) - 1); + } + if (st == PSA_SUCCESS) { + st = psa_key_derivation_input_bytes(&op, + PSA_KEY_DERIVATION_INPUT_PASSWORD, + secret, sizeof(secret) - 1); + } + } + else if (mode == KDF_MODE_SP800) { + /* Counter-mode KDF: the capacity is the L it binds into every block, + * so it has to be set before any input. */ + st = psa_key_derivation_set_capacity(&op, out_len); + if (st == PSA_SUCCESS) { + st = psa_key_derivation_input_bytes(&op, + PSA_KEY_DERIVATION_INPUT_SECRET, + aes_key, sizeof(aes_key)); + } + if (st == PSA_SUCCESS) { + st = psa_key_derivation_input_bytes(&op, + PSA_KEY_DERIVATION_INPUT_LABEL, salt, sizeof(salt) - 1); + } + if (st == PSA_SUCCESS) { + st = psa_key_derivation_input_bytes(&op, + PSA_KEY_DERIVATION_INPUT_CONTEXT, info, sizeof(info) - 1); + } + } + else { + st = PSA_SUCCESS; + if (mode != KDF_MODE_EXPAND) { + st = psa_key_derivation_input_bytes(&op, + PSA_KEY_DERIVATION_INPUT_SALT, salt, sizeof(salt) - 1); + } + if (st == PSA_SUCCESS && mode == KDF_MODE_EXPAND) { + /* Expand takes a PRK, so the secret must be exactly one digest. */ + st = psa_key_derivation_input_bytes(&op, + PSA_KEY_DERIVATION_INPUT_SECRET, prk, sizeof(prk)); + } + else if (st == PSA_SUCCESS) { + st = psa_key_derivation_input_bytes(&op, + PSA_KEY_DERIVATION_INPUT_SECRET, + secret, sizeof(secret) - 1); + } + if (st == PSA_SUCCESS && mode != KDF_MODE_EXTRACT) { + st = psa_key_derivation_input_bytes(&op, + PSA_KEY_DERIVATION_INPUT_INFO, info, sizeof(info) - 1); + } + } + + if (st == PSA_SUCCESS) + st = psa_key_derivation_output_bytes(&op, out, out_len); + + psa_key_derivation_abort(&op); + if (st == PSA_ERROR_NOT_SUPPORTED) + return -1; + if (st != PSA_SUCCESS) + return fail(label, st); + return 0; +} + +/* One derivation against a fresh count, so a devId dropped from a single KDF + * call site shows up. check_families() cannot see it: the hash, HMAC and CMAC + * counters are already non-zero from the other exercises. */ +static int check_one_kdf(psa_algorithm_t alg, int mode, size_t out_len, + const char *label, int algo_type) +{ + int ret; + + reset_counts(expect_devid); + + ret = run_derivation(alg, mode, out_len, label); + if (ret < 0) { + printf(" %-14s not supported in this build\n", label); + return 0; + } + if (ret != 0) + return ret; + + if (seen[algo_type] == 0) { + printf("FAIL %s reached no %s operation on devId %d\n", label, + algo_name(algo_type), expect_devid); + return 1; + } + if (seen_wrong_devid != 0) { + printf("FAIL %s carried a devId other than %d\n", label, + expect_devid); + return 1; + } + + printf(" %-14s dispatched %d %s operation(s)\n", label, + seen[algo_type], algo_name(algo_type)); + return 0; +} + +static int check_kdf_dispatch(void) +{ + int saved = expect_devid; + int ret = 0; + + printf("kdf dispatch:\n"); + + ret |= check_one_kdf(PSA_ALG_HKDF(PSA_ALG_SHA_256), KDF_MODE_HKDF, + 32u, "hkdf", WC_ALGO_TYPE_HMAC); + ret |= check_one_kdf(PSA_ALG_HKDF_EXTRACT(PSA_ALG_SHA_256), + KDF_MODE_EXTRACT, 32u, "hkdf-extract", + WC_ALGO_TYPE_HMAC); + ret |= check_one_kdf(PSA_ALG_HKDF_EXPAND(PSA_ALG_SHA_256), + KDF_MODE_EXPAND, 32u, "hkdf-expand", + WC_ALGO_TYPE_HMAC); + ret |= check_one_kdf(PSA_ALG_PBKDF2_HMAC(PSA_ALG_SHA_256), + KDF_MODE_PBKDF2, 32u, "pbkdf2", WC_ALGO_TYPE_HMAC); +#ifdef WOLFSSL_CMAC + ret |= check_one_kdf(PSA_ALG_SP800_108_COUNTER_CMAC, KDF_MODE_SP800, + 32u, "sp800-108-cmac", WC_ALGO_TYPE_CMAC); +#endif + + reset_counts(saved); + return ret; +} + +static int exercise_all(void) +{ + int ret = 0; + + ret |= exercise_random(); + ret |= exercise_hash(); + ret |= exercise_mac(PSA_ALG_HMAC(PSA_ALG_SHA_256), PSA_KEY_TYPE_HMAC, + 32, "hmac"); +#ifdef WOLFSSL_CMAC + ret |= exercise_mac(PSA_ALG_CMAC, PSA_KEY_TYPE_AES, 16, "cmac"); +#endif + ret |= exercise_cipher(); +#if !defined(NO_AES) && defined(HAVE_AES_KEYWRAP) + ret |= exercise_keywrap(); +#endif + ret |= exercise_ecdsa(); +#ifdef HAVE_ED25519 + ret |= exercise_eddsa(); +#endif +#ifndef NO_RSA + ret |= exercise_rsa(); +#endif + + return ret; +} + +static int require_seen(int algo_type) +{ + if (seen[algo_type] == 0) { + printf("FAIL no %s operation reached the callback\n", + algo_name(algo_type)); + return 1; + } + printf(" %-6s dispatched %d time(s)\n", algo_name(algo_type), + seen[algo_type]); + return 0; +} + +/* A device that generates the key but keeps the scalar leaves the private + * key at zero. Exporting that would hand the caller an all-zero private key, + * so psa_generate_key() must fail instead. Exercises the guards in + * psa_asymmetric_generate_key_ecc(), _ed25519() and _x25519(). + * + * Ed448 has no case here on purpose: wc_ed448_make_key() carries no + * wc_CryptoCb_* dispatch at all, only sign and verify, so no device can hold + * an Ed448 scalar and the guard would be unreachable. */ +static int check_keygen_silent_device(const char *label, int pk_type, + psa_key_type_t key_type, size_t bits, + psa_key_usage_t usage, + psa_algorithm_t alg) +{ + psa_key_attributes_t attr = psa_key_attributes_init(); + psa_key_id_t key = 0xdeadbeef; + psa_status_t st; + int ret = 0; + + psa_set_key_type(&attr, key_type); + psa_set_key_bits(&attr, bits); + psa_set_key_usage_flags(&attr, usage); + psa_set_key_algorithm(&attr, alg); + + mute_keygen_pk_type = pk_type; + st = psa_generate_key(&attr, &key); + mute_keygen_pk_type = WC_PK_TYPE_NONE; + + /* HARDWARE_FAILURE specifically: without the guard the export downstream + * still fails, but with INVALID_ARGUMENT, which is indistinguishable from + * a caller passing bad attributes. */ + if (st == PSA_SUCCESS) { + printf("FAIL psa_generate_key reported success for a device that" + " kept the %s private scalar\n", label); + psa_destroy_key(key); + ret = 1; + } + else if (st != PSA_ERROR_HARDWARE_FAILURE) { + printf("FAIL psa_generate_key refused the device-held %s key as %d," + " expected PSA_ERROR_HARDWARE_FAILURE\n", label, (int)st); + ret = 1; + } + else { + printf("silent device: %s keygen refused\n", label); + } + + return ret; +} + +/* Every keygen path that can reach a device, so a guard deleted from any one + * of them fails the suite. */ +static int check_keygen_silent_device_all(void) +{ + int ret = 0; + +#ifdef HAVE_ECC + if (ret == 0) { + ret = check_keygen_silent_device("ecc", WC_PK_TYPE_EC_KEYGEN, + PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1), 256, + PSA_KEY_USAGE_SIGN_HASH, PSA_ALG_ECDSA(PSA_ALG_SHA_256)); + } + reset_counts(TEST_DEVID); +#endif +#ifdef HAVE_ED25519 + if (ret == 0) { + ret = check_keygen_silent_device("ed25519", WC_PK_TYPE_ED25519_KEYGEN, + PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS), + 255, PSA_KEY_USAGE_SIGN_MESSAGE, PSA_ALG_PURE_EDDSA); + } + reset_counts(TEST_DEVID); +#endif +#ifdef HAVE_CURVE25519 + if (ret == 0) { + ret = check_keygen_silent_device("x25519", + WC_PK_TYPE_CURVE25519_KEYGEN, + PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_MONTGOMERY), 255, + PSA_KEY_USAGE_DERIVE, PSA_ALG_ECDH); + } + reset_counts(TEST_DEVID); +#endif + + return ret; +} + +static int check_families(void) +{ + int ret = 0; + + ret |= require_seen(WC_ALGO_TYPE_HASH); + ret |= require_seen(WC_ALGO_TYPE_CIPHER); + ret |= require_seen(WC_ALGO_TYPE_PK); + ret |= require_seen(WC_ALGO_TYPE_HMAC); +#ifdef WOLFSSL_CMAC + ret |= require_seen(WC_ALGO_TYPE_CMAC); +#endif + if (seen[WC_ALGO_TYPE_RNG] == 0 && seen[WC_ALGO_TYPE_SEED] == 0) { + printf("FAIL no rng operation reached the callback\n"); + ret = 1; + } + else { + printf(" %-6s dispatched %d time(s)\n", algo_name(WC_ALGO_TYPE_RNG), + seen[WC_ALGO_TYPE_RNG]); + printf(" %-6s dispatched %d time(s)\n", algo_name(WC_ALGO_TYPE_SEED), + seen[WC_ALGO_TYPE_SEED]); + } + if (seen_wrong_devid != 0) { + printf("FAIL %d operation(s) carried a devId other than %d\n", + seen_wrong_devid, expect_devid); + ret = 1; + } + return ret; +} + +int main(void) +{ + int ret = 0; + psa_status_t st; + + st = psa_crypto_init(); + if (st != PSA_SUCCESS) + return fail("psa_crypto_init", st); + + /* Phase 1: no device registered and no explicit devId, so every wolfCrypt + * object must be built with INVALID_DEVID. */ + if (wolfPSA_GetDefaultDevID() != INVALID_DEVID) { + printf("FAIL default devId is not INVALID_DEVID with no device" + " registered\n"); + return 1; + } + + /* Phase 2: a registered device and still no explicit devId. wolfCrypt's + * own selection picks the only registered device, so every family must + * reach it. An algorithm that never ran its wc_*Init() carries devId 0 + * instead and goes missing here. */ + if (wc_CryptoCb_RegisterDevice(PROBE_DEVID, count_cb, NULL) != 0) { + printf("FAIL wc_CryptoCb_RegisterDevice(probe)\n"); + return 1; + } + + if (wolfPSA_GetDefaultDevID() != PROBE_DEVID) { + printf("FAIL default devId did not follow wolfCrypt device selection" + " (got %d, want %d)\n", wolfPSA_GetDefaultDevID(), + PROBE_DEVID); + ret = 1; + } + + if (ret == 0) { + printf("wolfCrypt device selection (devId %d):\n", PROBE_DEVID); + reset_counts(PROBE_DEVID); + ret = exercise_all(); + } + + if (ret == 0) { + ret = check_families(); + } + + /* Phase 3: an explicit devId must win over that selection. The probe + * device stays registered, so a stale read shows up as a dispatch on + * PROBE_DEVID rather than TEST_DEVID. */ + if (ret == 0 && wc_CryptoCb_RegisterDevice(TEST_DEVID, count_cb, + NULL) != 0) { + printf("FAIL wc_CryptoCb_RegisterDevice(test)\n"); + ret = 1; + } + + if (ret == 0 && wolfPSA_SetDefaultDevID(TEST_DEVID) != 0) { + printf("FAIL wolfPSA_SetDefaultDevID\n"); + ret = 1; + } + + if (ret == 0 && wolfPSA_GetDefaultDevID() != TEST_DEVID) { + printf("FAIL explicit devId did not override device selection\n"); + ret = 1; + } + + if (ret == 0) { + printf("explicit devId %d:\n", TEST_DEVID); + reset_counts(TEST_DEVID); + ret = exercise_all(); + } + + if (ret == 0) { + ret = check_families(); + } + + if (ret == 0) { + ret = check_kdf_dispatch(); + } + reset_counts(TEST_DEVID); + + if (ret == 0) { + ret = check_keygen_silent_device_all(); + } + + /* Phase 4: an explicit INVALID_DEVID is the opt-out, so nothing may + * dispatch even though both devices are still registered. */ + if (ret == 0 && wolfPSA_SetDefaultDevID(INVALID_DEVID) != 0) { + printf("FAIL wolfPSA_SetDefaultDevID(INVALID_DEVID)\n"); + ret = 1; + } + + if (ret == 0 && wolfPSA_GetDefaultDevID() != INVALID_DEVID) { + printf("FAIL INVALID_DEVID did not force local execution\n"); + ret = 1; + } + + if (ret == 0) { + reset_counts(INVALID_DEVID); + ret = exercise_all(); + } + + if (ret == 0 && seen_total != 0) { + printf("FAIL %d operation(s) dispatched after opting out\n", + seen_total); + ret = 1; + } + else if (ret == 0) { + printf("opt-out: no dispatches\n"); + } + + wc_CryptoCb_UnRegisterDevice(TEST_DEVID); + wc_CryptoCb_UnRegisterDevice(PROBE_DEVID); + + if (ret != 0) + return 1; + + printf("PSA devId crypto callback test: OK\n"); + return 0; +} diff --git a/wolfpsa/psa_engine.h b/wolfpsa/psa_engine.h index 28e33eb..daa78a8 100644 --- a/wolfpsa/psa_engine.h +++ b/wolfpsa/psa_engine.h @@ -55,11 +55,38 @@ extern "C" { WOLFSSL_LOCAL psa_status_t wc_error_to_psa_status(int ret); /* Default wolfCrypt devId threaded through wolfPSA's internal wc_*Init() - * calls. Defaults to INVALID_DEVID so that operations execute locally. - * Set to a registered crypto_cb devId (e.g. via wc_CryptoCb_RegisterDevice) - * to route every wolfPSA-issued wolfCrypt call through that callback — - * this is the integration hook for crypto offload backends such as - * wolfHSM or a hardware accelerator. Safe to call before psa_crypto_init(). + * calls. Set it to a registered crypto_cb devId (e.g. via + * wc_CryptoCb_RegisterDevice) to route every wolfPSA-issued wolfCrypt call + * through that callback, which is the integration hook for crypto offload + * backends such as wolfHSM or a hardware accelerator. Safe to call before + * psa_crypto_init(). + * + * Until this is called wolfPSA expresses no preference and wolfCrypt's own + * device selection applies (wc_CryptoCb_DefaultDevID(), so + * WOLFSSL_CAAM_DEVID, WC_USE_DEVID or the first registered device, unless + * the build sets WC_NO_DEFAULT_DEVID). Any explicit devId wins over that + * selection, INVALID_DEVID included: passing it forces every wolfPSA + * operation to run locally even when other devices are registered. + * + * Coverage: every algorithm whose wolfCrypt init function accepts a devId, + * which is AES, 3DES, RSA, ECC, Ed25519, Ed448, X25519, ML-DSA, + * ML-KEM, LMS, XMSS, SHA-1, the SHA-2 and SHA-3 families, SHAKE, HMAC, + * CMAC, HKDF, PBKDF2 and the RNG. X448, RIPEMD-160, MD5, Ascon and + * ChaCha20-Poly1305 always run locally, either because wolfCrypt exposes no + * devId for them or because it accepts one and ignores it. AES key wrap + * (PSA_ALG_KW) also stays local for now: wc_AesKeyWrap_ex() falls back to + * software when the device has no keywrap handler, and a device that + * installed the KEK through WOLF_CRYPTO_CB_AES_SETKEY leaves no software + * key schedule for that fallback to use, so the wrap would silently run + * under all-zero round keys. It joins the list once wolfCrypt refuses that + * fallback. + * + * A callback that does not implement a given algorithm returns + * CRYPTOCB_UNAVAILABLE and wolfCrypt falls back to software. One case needs + * more than that: a backend that keeps per-operation state in devCtx should + * be built with WOLF_CRYPTO_CB_COPY and WOLF_CRYPTO_CB_FREE, otherwise + * psa_hash_clone() duplicates the handle and psa_hash_abort() never + * releases it. * * Threading: the default devId is held in a process-global variable read * by every wolfPSA-internal wc_*Init() invocation. Callers must set it @@ -71,8 +98,10 @@ WOLFSSL_LOCAL psa_status_t wc_error_to_psa_status(int ret); * Returns 0 on success. */ WOLFSSL_API int wolfPSA_SetDefaultDevID(int devId); -/* Returns the devId previously set with wolfPSA_SetDefaultDevID() or - * INVALID_DEVID if none has been set. */ +/* Returns the devId wolfPSA passes to wolfCrypt: the value given to + * wolfPSA_SetDefaultDevID(), or wolfCrypt's own selection when that was + * never called. It is therefore not a test for whether a devId was + * configured, and it can name a device nobody asked wolfPSA to use. */ WOLFSSL_API int wolfPSA_GetDefaultDevID(void); #ifdef __cplusplus From 246bd10edfc2125c09ef011484a605e0e9c1bf30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Fri, 28 Aug 2026 12:20:41 +0200 Subject: [PATCH 2/3] Make the default devId atomic and reversible, and cover X448 The devId is read by roughly sixty wolfCrypt initializer call sites, so an application that changes it while other threads issue PSA calls had a formal data race on a plain int. Hold it in a wolfSSL_Atomic_Int instead. The existing WOLFPSA_LOCK mutex would have been the wrong instrument: wolfpsa_kdf_pbkdf2 re-reads the value once per block per iteration, so a PBKDF2 at cost 10000 would have paid ten thousand mutex round trips for a value that changes at most once in a program's life. Whether a devId has been configured folds into the same variable, because two separate ints cannot be updated atomically together and a reader could otherwise see the flag set against a stale value. WOLFPSA_DEVID_DEFAULT marks "wolfPSA expresses no preference", which keeps INVALID_DEVID available as the explicit request for local execution. It is public rather than a private sentinel because every setting is now reversible: an explicit devId and the INVALID_DEVID opt-out both give way to it. The header records what the atomic does not buy. It is not a barrier, so an operation already under way keeps the devId its wolfCrypt context was initialised with. It covers this setting only, not wolfCrypt's unsynchronized device table, so registration still belongs in single-threaded init or teardown. And on a SINGLE_THREADED or WOLFSSL_NO_ATOMICS build the accessors degrade to plain loads and stores, which the header says rather than promising defined behaviour everywhere. The setter also stops accepting what it cannot deliver. A real devId asks for an offload that does not exist in a library built without WOLF_CRYPTO_CB, so that now returns NOT_COMPILED_IN and leaves the default alone. It still cannot tell whether a devId names a registered device: wolfCrypt_Init() clears the callback table, so registration has to follow psa_crypto_init(), and refusing an as-yet-unregistered devId would forbid configuring wolfPSA before that point. That registration is reachable from the shared library through wolfPSA_RegisterCryptoCb() and wolfPSA_UnRegisterCryptoCb(). The wolfCrypt objects a devId selects live inside libwolfpsa, and exporting the raw wc_CryptoCb_* names instead would have bound them by link order, so an application that also links libwolfssl could have registered into the wrong device table. X448 joins the list of algorithms that carry the devId. It was excluded only because wolfCrypt had no curve448 callbacks; it has them now, so psa_montgomery.c threads the devId through wc_curve448_init_ex(). Deriving an X448 public key moves off wc_curve448_make_pub(), which takes raw buffers rather than a key and is therefore offered to whichever device happens to be registered, ignoring the configured devId even when the caller had forced local execution. X25519 has the same shape and no such escape, so the header documents it rather than pretending otherwise: wc_CryptoCb_Curve25519MakePub() takes no devId at all and falls back to the device at index 0, and wc_curve25519_export_public_ex() derives a missing public point through that same call. Key generation and key agreement are unaffected. Separately, X448 key generation gains the private-scalar check the other generate paths carry, because wc_curve448_make_key() does dispatch through wc_CryptoCb_Curve448Gen(). A device that reports success while keeping the scalar is refused as HARDWARE_FAILURE rather than as an argument error. Ed448 needs no such check: wc_ed448_make_key() has no callback dispatch at all, only sign and verify, so the condition is unreachable there. The muted-device case in psa_devid_cryptocb_test gains an X448 entry beside the three it already carries. Passing a devId to the HMAC path exposed a second defect: wc_HmacSetKey() stores the caller's buffer in Hmac.keyRaw rather than copying it, and psa_mac_setup freed that buffer while the operation lived on, so a device read freed memory on the next update. The operation owns its key material now, under WOLF_CRYPTO_CB only, since that is the build where keyRaw exists; psa_mac_abort() shares the same teardown instead of duplicating it. CI runs psa_devid_cryptocb_test a second time under AddressSanitizer, since reading freed memory is silent otherwise. The new case in psa_14_misc_test covers the setter, asserting only what holds in every build: the setter's verdict and the getter's report agree. It cannot key off its own WOLF_CRYPTO_CB, because that TU is compiled without user_settings.h and so says nothing about how the linked library was built. --- .github/workflows/test-psa-api.yml | 9 + CHANGELOG.md | 28 ++- src/psa_engine.c | 74 +++++-- src/psa_mac.c | 53 ++++- src/psa_montgomery.c | 41 +++- test/README.md | 10 +- test/psa_server/psa_14_misc_test.c | 66 +++++++ test/psa_server/psa_api_test_user_settings.h | 7 +- test/psa_server/psa_devid_cryptocb_test.c | 192 ++++++++++++++++++- wolfpsa.map | 8 + wolfpsa/psa_engine.h | 118 ++++++++---- 11 files changed, 503 insertions(+), 103 deletions(-) diff --git a/.github/workflows/test-psa-api.yml b/.github/workflows/test-psa-api.yml index 4d4e1c2..f0c4143 100644 --- a/.github/workflows/test-psa-api.yml +++ b/.github/workflows/test-psa-api.yml @@ -168,3 +168,12 @@ jobs: rm -rf test/.store ./test/$t done + + # The crypto callback reads the HMAC key out of Hmac.keyRaw, which + # wolfCrypt borrows rather than copies. Only a sanitizer notices when + # the operation outlives that buffer. + - name: Run the devId test under AddressSanitizer + run: | + make -C test psa_devid_cryptocb_test ASAN=1 + rm -rf test/.store + ./test/psa_devid_cryptocb_test diff --git a/CHANGELOG.md b/CHANGELOG.md index 6648297..721801e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,22 +58,18 @@ wolfSSL master. evict). - Crypto callback offload: `wolfPSA_SetDefaultDevID()` now reaches every algorithm whose wolfCrypt initializer accepts a devId, adding RSA, ECC, - Ed25519, Ed448, X25519, AES-KW, CMAC, HKDF, PBKDF2, the RNG and the - SHA-1/SHA-2 families to the coverage. X448, RIPEMD-160, MD5, Ascon and - ChaCha20-Poly1305 stay local, either because wolfCrypt exposes no devId - for them or because it ignores the one it accepts. Leaving the default at - INVALID_DEVID now defers to `wc_CryptoCb_DefaultDevID()`, so a build that - relied on wolfCrypt selecting a device by itself (CAAM, `WC_USE_DEVID`, or - the first registered device) keeps that behaviour; calling - `wolfPSA_SetDefaultDevID(INVALID_DEVID)` explicitly forces local execution - instead. PSA_ALG_DETERMINISTIC_ECDSA always signs locally, because the - crypto callback contract cannot express the RFC 6979 requirement. - `cryptocb.c` is part of the build so a library can be compiled with - `WOLF_CRYPTO_CB`. -- Fixed: the HMAC path of `psa_mac_*` never called `wc_HmacInit()`, so the - operation ran with devId 0 instead of the configured default. A crypto - callback registered on device 0 captured wolfPSA's HMACs while every - other algorithm stayed local. + Ed25519, Ed448, X25519, X448, CMAC, HKDF, PBKDF2, the RNG and the + SHA-1/SHA-2 families. The setting is held in one atomic, an unset devId + defers to `wc_CryptoCb_DefaultDevID()`, and `WOLFPSA_DEVID_DEFAULT` + restores that, so no setting is a one-way door. New + `wolfPSA_RegisterCryptoCb()` / `wolfPSA_UnRegisterCryptoCb()` register + against the device table wolfPSA dispatches through. Deterministic ECDSA, + AES-KW, RIPEMD-160, MD5, Ascon and ChaCha20-Poly1305 stay local; + `wolfpsa/psa_engine.h` documents why, plus the X25519 and + `WOLF_CRYPTO_CB_FIND` caveats. +- Fixed: the HMAC path of `psa_mac_*` never called `wc_HmacInit()`, so it + ran with devId 0 and a callback registered on device 0 captured wolfPSA's + HMACs while every other algorithm stayed local. - Optional thread-safe key store: with `WOLFPSA_THREAD_SAFE` a single mutex built on wolfCrypt's portable `wc_*Mutex` API (created in `psa_crypto_init()`) guards the volatile-key list and id counter for concurrent PSA callers; a diff --git a/src/psa_engine.c b/src/psa_engine.c index 0e6713f..87631dc 100644 --- a/src/psa_engine.c +++ b/src/psa_engine.c @@ -31,37 +31,79 @@ #include #include #include +#include #include /* Runtime-settable devId threaded through every wolfPSA-internal - * wc_*Init()/wc_NewRsaKey() call. INVALID_DEVID means no wolfPSA-specific - * choice has been made, which defers to wolfCrypt's own device selection. */ -static int wolfPSA_default_devid = INVALID_DEVID; - -/* Distinguishes "never configured", which defers to wolfCrypt, from an - * explicit INVALID_DEVID, which is how a caller forces local execution. */ -static int wolfPSA_devid_configured = 0; + * wc_*Init()/wc_NewRsaKey() call. WOLFPSA_DEVID_DEFAULT means wolfPSA + * expresses no preference; keeping that state in the same variable means a + * reader takes one atomic load and can never observe a half-updated pair. + * Atomic because the setter may run while PSA operations are in flight on + * other threads, and the load costs nothing next to the crypto it precedes. + */ +static wolfSSL_Atomic_Int wolfPSA_default_devid = + WOLFSSL_ATOMIC_INITIALIZER(WOLFPSA_DEVID_DEFAULT); int wolfPSA_SetDefaultDevID(int devId) { - wolfPSA_default_devid = devId; - wolfPSA_devid_configured = 1; +#ifndef WOLF_CRYPTO_CB + /* The dispatch a real devId selects is compiled out of this library, so + * accepting one would promise an offload that cannot happen. The two + * values that ask for no offload stay valid. */ + if (devId != INVALID_DEVID && devId != WOLFPSA_DEVID_DEFAULT) { + return NOT_COMPILED_IN; + } +#endif + + WOLFSSL_ATOMIC_STORE(wolfPSA_default_devid, devId); return 0; } int wolfPSA_GetDefaultDevID(void) { + int devId = (int)WOLFSSL_ATOMIC_LOAD(wolfPSA_default_devid); + #ifdef WOLF_CRYPTO_CB - /* Several wolfCrypt initializers (the SHA-2 family, and wc_ecc_init or - * wc_InitCmac on CAAM targets) pick a device themselves rather than - * defaulting to INVALID_DEVID. Deferring to the same selection keeps a - * caller that never sets a wolfPSA devId on the behaviour it had before - * wolfPSA started passing one. */ - if (!wolfPSA_devid_configured) { + /* Several wolfCrypt initializers pick a device themselves rather than + * defaulting to INVALID_DEVID: the SHA-2 family through + * wc_CryptoCb_DefaultDevID(), and wc_ecc_init or wc_InitCmac on CAAM + * targets. Deferring to the same selection is what keeps those on the + * behaviour they had before wolfPSA passed a devId. For the rest, whose + * plain initializers do pin INVALID_DEVID, it widens the default from + * local to whatever wolfCrypt selects, so that the whole library follows + * one policy rather than splitting by algorithm. */ + if (devId == WOLFPSA_DEVID_DEFAULT) { return wc_CryptoCb_DefaultDevID(); } +#else + if (devId == WOLFPSA_DEVID_DEFAULT) { + return INVALID_DEVID; + } +#endif + return devId; +} + +int wolfPSA_RegisterCryptoCb(int devId, wolfPSA_CryptoCbFunc cb, void *ctx) +{ +#ifdef WOLF_CRYPTO_CB + return wc_CryptoCb_RegisterDevice(devId, cb, ctx); +#else + (void)devId; + (void)cb; + (void)ctx; + return NOT_COMPILED_IN; +#endif +} + +int wolfPSA_UnRegisterCryptoCb(int devId) +{ +#ifdef WOLF_CRYPTO_CB + wc_CryptoCb_UnRegisterDevice(devId); + return 0; +#else + (void)devId; + return NOT_COMPILED_IN; #endif - return wolfPSA_default_devid; } /* wolfCrypt error code to PSA status code conversion */ diff --git a/src/psa_mac.c b/src/psa_mac.c index 0dc4def..3a86513 100644 --- a/src/psa_mac.c +++ b/src/psa_mac.c @@ -55,6 +55,17 @@ typedef struct wolfpsa_mac_ctx { size_t mac_length; size_t full_length; wolfpsa_mac_type_t type; +#ifdef WOLF_CRYPTO_CB + /* wc_HmacSetKey() borrows the key buffer rather than copying it when + * WOLF_CRYPTO_CB is on (hmac.c stores it in Hmac.keyRaw), and a crypto + * callback reads it on every update. The caller's copy is freed as soon + * as setup returns, so the operation has to own the bytes itself. A + * build without the callbacks has no keyRaw to dangle, so it passes + * key_data straight through rather than keeping a second copy of the + * key alive for the whole operation. */ + uint8_t *key; + size_t key_length; +#endif union { Hmac hmac; #ifdef WOLFSSL_CMAC @@ -81,6 +92,15 @@ static void wolfpsa_mac_free_underlying(wolfpsa_mac_ctx_t *ctx) wc_CmacFree(&ctx->ctx.cmac); } #endif +#ifdef WOLF_CRYPTO_CB + /* Released after the wolfCrypt context, which may still reference it. */ + if (ctx->key != NULL) { + wc_ForceZero(ctx->key, ctx->key_length); + XFREE(ctx->key, NULL, DYNAMIC_TYPE_TMP_BUFFER); + ctx->key = NULL; + ctx->key_length = 0; + } +#endif } psa_status_t psa_mac_abort(psa_mac_operation_t *operation); @@ -291,15 +311,37 @@ static psa_status_t wolfpsa_mac_setup(psa_mac_operation_t *operation, int hash_type = wolfpsa_hash_type_from_alg(alg); if (hash_type == WC_HASH_TYPE_NONE) { wolfpsa_forcezero_free_key_data(key_data, key_data_length); + wc_ForceZero(ctx, sizeof(*ctx)); XFREE(ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); return PSA_ERROR_NOT_SUPPORTED; } + ctx->type = WOLFPSA_MAC_HMAC; +#ifdef WOLF_CRYPTO_CB + ctx->key = (uint8_t *)XMALLOC(key_data_length == 0 ? 1 : + key_data_length, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (ctx->key == NULL) { + wolfpsa_forcezero_free_key_data(key_data, key_data_length); + wc_ForceZero(ctx, sizeof(*ctx)); + XFREE(ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return PSA_ERROR_INSUFFICIENT_MEMORY; + } + if (key_data_length > 0) { + XMEMCPY(ctx->key, key_data, key_data_length); + } + ctx->key_length = key_data_length; +#endif + ret = wc_HmacInit(&ctx->ctx.hmac, NULL, wolfPSA_GetDefaultDevID()); if (ret == 0) { +#ifdef WOLF_CRYPTO_CB + ret = wc_HmacSetKey(&ctx->ctx.hmac, hash_type, ctx->key, + (word32)ctx->key_length); +#else ret = wc_HmacSetKey(&ctx->ctx.hmac, hash_type, key_data, (word32)key_data_length); +#endif } - ctx->type = WOLFPSA_MAC_HMAC; } #ifdef WOLFSSL_CMAC else if (PSA_ALG_IS_BLOCK_CIPHER_MAC(alg) && @@ -559,14 +601,7 @@ psa_status_t psa_mac_abort(psa_mac_operation_t *operation) } if (ctx != NULL) { - if (ctx->type == WOLFPSA_MAC_HMAC) { - wc_HmacFree(&ctx->ctx.hmac); - } -#ifdef WOLFSSL_CMAC - if (ctx->type == WOLFPSA_MAC_CMAC) { - wc_CmacFree(&ctx->ctx.cmac); - } -#endif + wolfpsa_mac_free_underlying(ctx); wc_ForceZero(ctx, sizeof(*ctx)); XFREE(ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); operation->opaque = (uintptr_t)NULL; diff --git a/src/psa_montgomery.c b/src/psa_montgomery.c index 39edcce..808beb4 100644 --- a/src/psa_montgomery.c +++ b/src/psa_montgomery.c @@ -295,7 +295,8 @@ psa_status_t psa_asymmetric_generate_key_x448(psa_key_type_t key_type, priv_len = (word32)private_key_size; pub_len = (word32)public_key_size; - ret = wc_curve448_init(&key); + ret = wc_curve448_init_ex(&key, NULL, + wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } @@ -306,6 +307,12 @@ psa_status_t psa_asymmetric_generate_key_x448(psa_key_type_t key_type, } ret = wc_curve448_make_key(&rng, CURVE448_KEY_SIZE, &key); + /* Same offload caveat as the X25519 path above. */ + if ((ret == 0) && (!key.privSet)) { + wc_FreeRng(&rng); + wc_curve448_free(&key); + return PSA_ERROR_HARDWARE_FAILURE; + } if (ret == 0) { ret = wc_curve448_export_private_raw_ex(&key, private_key, &priv_len, EC448_LITTLE_ENDIAN); @@ -335,7 +342,7 @@ psa_status_t psa_asymmetric_export_public_key_x448(psa_key_type_t key_type, size_t *output_length) { int ret; - uint8_t priv[CURVE448_KEY_SIZE]; + word32 pub_len; if ((key_type != PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_MONTGOMERY) && key_type != PSA_KEY_TYPE_ECC_PUBLIC_KEY(PSA_ECC_FAMILY_MONTGOMERY)) || @@ -353,12 +360,24 @@ psa_status_t psa_asymmetric_export_public_key_x448(psa_key_type_t key_type, if (key_buffer_size != CURVE448_KEY_SIZE) { return PSA_ERROR_INVALID_ARGUMENT; } - XMEMCPY(priv, key_buffer, CURVE448_KEY_SIZE); - priv[0] &= 252; - priv[55] |= 128; - ret = wc_curve448_make_pub(CURVE448_KEY_SIZE, output, - CURVE448_KEY_SIZE, priv); - wc_ForceZero(priv, sizeof(priv)); + curve448_key key; + + /* Derive through a key object rather than wc_curve448_make_pub(): + * the keyless form is documented as routable to whichever device + * happens to be registered, which would ignore the configured devId + * and defeat forcing local execution. */ + ret = wc_curve448_init_ex(&key, NULL, wolfPSA_GetDefaultDevID()); + if (ret == 0) { + /* import_private_ex applies the curve448 clamp itself */ + ret = wc_curve448_import_private_ex(key_buffer, CURVE448_KEY_SIZE, + &key, EC448_LITTLE_ENDIAN); + if (ret == 0) { + pub_len = CURVE448_KEY_SIZE; + ret = wc_curve448_export_public_ex(&key, output, &pub_len, + EC448_LITTLE_ENDIAN); + } + wc_curve448_free(&key); + } } else { if (key_buffer_size != CURVE448_KEY_SIZE) { @@ -409,11 +428,13 @@ psa_status_t psa_asymmetric_key_agreement_x448( } out_len = (word32)output_size; - ret = wc_curve448_init(&priv); + ret = wc_curve448_init_ex(&priv, NULL, + wolfPSA_GetDefaultDevID()); if (ret != 0) { return wc_error_to_psa_status(ret); } - ret = wc_curve448_init(&pub); + ret = wc_curve448_init_ex(&pub, NULL, + wolfPSA_GetDefaultDevID()); if (ret != 0) { wc_curve448_free(&priv); return wc_error_to_psa_status(ret); diff --git a/test/README.md b/test/README.md index bdf3a1a..a786038 100644 --- a/test/README.md +++ b/test/README.md @@ -16,10 +16,14 @@ This directory contains standalone tests and demos for wolfPSA integration. - `psa_devid_cryptocb_test` - Checks that every algorithm family reaches a registered crypto callback, both through wolfCrypt's own device selection and through an explicit - `wolfPSA_SetDefaultDevID()`, and that an explicit `INVALID_DEVID` keeps - every operation local. + `wolfPSA_SetDefaultDevID()`, that an explicit `INVALID_DEVID` keeps every + operation local, and that `WOLFPSA_DEVID_DEFAULT` hands the choice back. - Links against its own `WOLF_CRYPTO_CB` build of the library, so it needs no - prebuilt wolfSSL. + prebuilt wolfSSL. That second build is why the test is not part of the + default `all` target: build it by name with + `make -C test psa_devid_cryptocb_test`. Its object directory is scoped to + the flag set (`build-cryptocb`, `build-cryptocb-asan`), so an `ASAN=1` + build never reuses uninstrumented objects. - Source: `psa_server/psa_devid_cryptocb_test.c` - `psa_tls_server` + `psa_tls_client` diff --git a/test/psa_server/psa_14_misc_test.c b/test/psa_server/psa_14_misc_test.c index 12f6822..1add69a 100644 --- a/test/psa_server/psa_14_misc_test.c +++ b/test/psa_server/psa_14_misc_test.c @@ -31,6 +31,8 @@ #include #include +#include +#include static int expect_status(const char *label, psa_status_t status, psa_status_t expected) @@ -989,6 +991,66 @@ static int test_volatile_attributes_reuse(void) return ret; } +/* Case 17: the default devId setter agrees with how the library was built. + * INVALID_DEVID and WOLFPSA_DEVID_DEFAULT ask for no offload and must always + * be accepted; a real devId is only meaningful when the dispatch was compiled + * in. Which of those two the library does is not decided here: this TU is + * compiled without user_settings.h, so its own WOLF_CRYPTO_CB is unrelated + * to the flags libwolfpsa was built with, and test/Makefile already builds a + * sibling archive with the callbacks on. So accept either answer and assert + * the invariant that holds in both: the setter's verdict and the getter's + * report agree. */ +static int test_default_devid_setter(void) +{ + int ret = 0; + int st; + + st = wolfPSA_SetDefaultDevID(INVALID_DEVID); + if (st != 0) { + printf("FAIL devid setter rejected INVALID_DEVID ret=%d\n", st); + ret = 1; + } + + if (ret == 0 && wolfPSA_GetDefaultDevID() != INVALID_DEVID) { + printf("FAIL devid getter did not report the forced local devId\n"); + ret = 1; + } + + if (ret == 0) { + st = wolfPSA_SetDefaultDevID(7); + if (st == 0) { + /* Accepted: the library dispatches, so it has to use the value. */ + if (wolfPSA_GetDefaultDevID() != 7) { + printf("FAIL accepted devId was not reported back\n"); + ret = 1; + } + } + else if (st == WC_NO_ERR_TRACE(NOT_COMPILED_IN)) { + /* Refused: no dispatch was compiled in, so nothing may change. */ + if (wolfPSA_GetDefaultDevID() != INVALID_DEVID) { + printf("FAIL rejected devId still changed the default\n"); + ret = 1; + } + } + else { + printf("FAIL devid setter returned neither success nor" + " NOT_COMPILED_IN ret=%d\n", st); + ret = 1; + } + } + + /* Hand the choice back to wolfCrypt, so this case leaves the process in + * the state it found it in. */ + st = wolfPSA_SetDefaultDevID(WOLFPSA_DEVID_DEFAULT); + if (st != 0) { + printf("FAIL devid setter rejected WOLFPSA_DEVID_DEFAULT ret=%d\n", + st); + ret = 1; + } + + return ret; +} + int main(void) { psa_status_t st; @@ -1063,6 +1125,10 @@ int main(void) if (test_volatile_attributes_reuse() != 0) return 1; + /* Case 17: the default devId setter agrees with the build */ + if (test_default_devid_setter() != 0) + return 1; + printf("PSA 1.4 misc test: OK\n"); return 0; } diff --git a/test/psa_server/psa_api_test_user_settings.h b/test/psa_server/psa_api_test_user_settings.h index 5ac64de..bb33f06 100644 --- a/test/psa_server/psa_api_test_user_settings.h +++ b/test/psa_server/psa_api_test_user_settings.h @@ -30,7 +30,12 @@ extern "C" { #define NO_OLD_TLS #define NO_FILESYSTEM -#define HAVE_GETRANDOM +/* Valued 1 to match the autotools config.h of the sibling wolfSSL, which + * defines it the same way on Linux. A test that includes a wolfpsa header + * pulls that config.h in as well (the headers include it under + * HAVE_CONFIG_H), and a bare #define here would collide with it under + * -Werror. Identical redefinitions are permitted; differing ones are not. */ +#define HAVE_GETRANDOM 1 #define HAVE_HASHDRBG #define HAVE_ECC diff --git a/test/psa_server/psa_devid_cryptocb_test.c b/test/psa_server/psa_devid_cryptocb_test.c index 9bc0c4d..58ff5fc 100644 --- a/test/psa_server/psa_devid_cryptocb_test.c +++ b/test/psa_server/psa_devid_cryptocb_test.c @@ -80,8 +80,15 @@ static const char *algo_name(int algo_type) } } +/* Sink for the borrowed HMAC key, so the read below is not optimised away. */ +static volatile byte key_sink; + /* Records the dispatch and declines it, so wolfCrypt falls back to software - * and the PSA results stay correct. */ + * and the PSA results stay correct. + * + * The HMAC arm reads the key the way a real backend would. wc_HmacSetKey() + * stores the caller's buffer in Hmac.keyRaw rather than copying it, so this + * is what catches the operation outliving the key material. */ static int count_cb(int devId, wc_CryptoInfo *info, void *ctx) { (void)ctx; @@ -95,6 +102,14 @@ static int count_cb(int devId, wc_CryptoInfo *info, void *ctx) } seen_total++; + if (info != NULL && info->algo_type == WC_ALGO_TYPE_HMAC && + info->hmac.hmac != NULL && info->hmac.hmac->keyRaw != NULL) { + word16 i; + for (i = 0; i < info->hmac.hmac->keyLen; i++) { + key_sink = info->hmac.hmac->keyRaw[i]; + } + } + if (mute_keygen_pk_type != WC_PK_TYPE_NONE && info != NULL && info->algo_type == WC_ALGO_TYPE_PK && info->pk.type == mute_keygen_pk_type) { @@ -230,6 +245,77 @@ static int exercise_ecdsa(void) return 0; } +#ifdef WOLFSSL_ECDSA_DETERMINISTIC_K +/* PSA_ALG_DETERMINISTIC_ECDSA promises RFC 6979, and the crypto callback + * contract carries no deterministic flag, so wolfPSA pins that algorithm to + * local execution. Two things have to hold: the signature is reproducible, + * and the sign never reaches the device even though one is selected. The + * second is the real guard, because the declining callback in this test + * leaves the software path reproducible either way. + * + * Called with a device selected and the counters freshly reset. */ +static int check_deterministic_ecdsa_stays_local(void) +{ + uint8_t digest[32]; + uint8_t sig_a[PSA_SIGNATURE_MAX_SIZE]; + uint8_t sig_b[PSA_SIGNATURE_MAX_SIZE]; + size_t len_a = 0; + size_t len_b = 0; + psa_algorithm_t alg = PSA_ALG_DETERMINISTIC_ECDSA(PSA_ALG_SHA_256); + psa_key_attributes_t attr = psa_key_attributes_init(); + psa_key_id_t key = PSA_KEY_ID_NULL; + psa_status_t st; + int ret = 0; + + XMEMSET(digest, 0x33, sizeof(digest)); + + psa_set_key_type(&attr, + PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); + psa_set_key_bits(&attr, 256); + psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_SIGN_HASH); + psa_set_key_algorithm(&attr, alg); + + st = psa_generate_key(&attr, &key); + if (st != PSA_SUCCESS) + return fail("deterministic ecdsa generate", st); + + /* Key generation is allowed to offload; only the signing is measured. */ + reset_counts(expect_devid); + + st = psa_sign_hash(key, alg, digest, sizeof(digest), + sig_a, sizeof(sig_a), &len_a); + if (st != PSA_SUCCESS) { + ret = fail("deterministic ecdsa sign 1", st); + } + + if (ret == 0) { + st = psa_sign_hash(key, alg, digest, sizeof(digest), + sig_b, sizeof(sig_b), &len_b); + if (st != PSA_SUCCESS) + ret = fail("deterministic ecdsa sign 2", st); + } + + if (ret == 0 && (len_a != len_b || + XMEMCMP(sig_a, sig_b, len_a) != 0)) { + printf("FAIL deterministic ECDSA produced two different" + " signatures\n"); + ret = 1; + } + + if (ret == 0 && seen[WC_ALGO_TYPE_PK] != 0) { + printf("FAIL deterministic ECDSA dispatched %d pk operation(s) to a" + " device\n", seen[WC_ALGO_TYPE_PK]); + ret = 1; + } + else if (ret == 0) { + printf("deterministic ECDSA: stayed local\n"); + } + + psa_destroy_key(key); + return ret; +} +#endif /* WOLFSSL_ECDSA_DETERMINISTIC_K */ + #ifdef HAVE_ED25519 static int exercise_eddsa(void) { @@ -324,6 +410,48 @@ static int exercise_keywrap(void) } #endif /* !NO_AES && HAVE_AES_KEYWRAP */ +#ifdef HAVE_CURVE448 +/* X448 keygen plus agreement, so the curve448 crypto callbacks wolfCrypt + * gained are actually exercised rather than assumed. */ +static int exercise_x448(void) +{ + uint8_t peer[56]; + uint8_t secret[56]; + size_t peer_len = 0; + size_t secret_len = 0; + psa_key_attributes_t attr = psa_key_attributes_init(); + psa_key_id_t key = PSA_KEY_ID_NULL; + psa_status_t st; + int ret = 0; + + psa_set_key_type(&attr, + PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_MONTGOMERY)); + psa_set_key_bits(&attr, 448); + psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_DERIVE | + PSA_KEY_USAGE_EXPORT); + psa_set_key_algorithm(&attr, PSA_ALG_ECDH); + + st = psa_generate_key(&attr, &key); + if (st != PSA_SUCCESS) + return fail("x448 generate", st); + + st = psa_export_public_key(key, peer, sizeof(peer), &peer_len); + if (st != PSA_SUCCESS) { + ret = fail("x448 export public", st); + } + + if (ret == 0) { + st = psa_raw_key_agreement(PSA_ALG_ECDH, key, peer, peer_len, + secret, sizeof(secret), &secret_len); + if (st != PSA_SUCCESS) + ret = fail("psa_raw_key_agreement x448", st); + } + + psa_destroy_key(key); + return ret; +} +#endif /* HAVE_CURVE448 */ + #ifndef NO_RSA static int exercise_rsa(void) { @@ -528,6 +656,9 @@ static int exercise_all(void) #ifdef HAVE_ED25519 ret |= exercise_eddsa(); #endif +#ifdef HAVE_CURVE448 + ret |= exercise_x448(); +#endif #ifndef NO_RSA ret |= exercise_rsa(); #endif @@ -550,7 +681,7 @@ static int require_seen(int algo_type) /* A device that generates the key but keeps the scalar leaves the private * key at zero. Exporting that would hand the caller an all-zero private key, * so psa_generate_key() must fail instead. Exercises the guards in - * psa_asymmetric_generate_key_ecc(), _ed25519() and _x25519(). + * psa_asymmetric_generate_key_ecc(), _ed25519(), _x25519() and _x448(). * * Ed448 has no case here on purpose: wc_ed448_make_key() carries no * wc_CryptoCb_* dispatch at all, only sign and verify, so no device can hold @@ -626,6 +757,14 @@ static int check_keygen_silent_device_all(void) } reset_counts(TEST_DEVID); #endif +#ifdef HAVE_CURVE448 + if (ret == 0) { + ret = check_keygen_silent_device("x448", WC_PK_TYPE_CURVE448_KEYGEN, + PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_MONTGOMERY), 448, + PSA_KEY_USAGE_DERIVE, PSA_ALG_ECDH); + } + reset_counts(TEST_DEVID); +#endif return ret; } @@ -680,8 +819,11 @@ int main(void) * own selection picks the only registered device, so every family must * reach it. An algorithm that never ran its wc_*Init() carries devId 0 * instead and goes missing here. */ - if (wc_CryptoCb_RegisterDevice(PROBE_DEVID, count_cb, NULL) != 0) { - printf("FAIL wc_CryptoCb_RegisterDevice(probe)\n"); + /* Through wolfPSA's own wrappers, which is what a shared-library user + * has to use: a bare wc_CryptoCb_RegisterDevice() binds by link order + * and can land in libwolfssl's device table instead of this one. */ + if (wolfPSA_RegisterCryptoCb(PROBE_DEVID, count_cb, NULL) != 0) { + printf("FAIL wolfPSA_RegisterCryptoCb(probe)\n"); return 1; } @@ -705,9 +847,9 @@ int main(void) /* Phase 3: an explicit devId must win over that selection. The probe * device stays registered, so a stale read shows up as a dispatch on * PROBE_DEVID rather than TEST_DEVID. */ - if (ret == 0 && wc_CryptoCb_RegisterDevice(TEST_DEVID, count_cb, - NULL) != 0) { - printf("FAIL wc_CryptoCb_RegisterDevice(test)\n"); + if (ret == 0 && wolfPSA_RegisterCryptoCb(TEST_DEVID, count_cb, + NULL) != 0) { + printf("FAIL wolfPSA_RegisterCryptoCb(test)\n"); ret = 1; } @@ -731,6 +873,13 @@ int main(void) ret = check_families(); } +#ifdef WOLFSSL_ECDSA_DETERMINISTIC_K + if (ret == 0) { + ret = check_deterministic_ecdsa_stays_local(); + } + reset_counts(TEST_DEVID); +#endif + if (ret == 0) { ret = check_kdf_dispatch(); } @@ -766,8 +915,33 @@ int main(void) printf("opt-out: no dispatches\n"); } - wc_CryptoCb_UnRegisterDevice(TEST_DEVID); - wc_CryptoCb_UnRegisterDevice(PROBE_DEVID); + /* Phase 5: WOLFPSA_DEVID_DEFAULT hands the choice back to wolfCrypt, so + * the opt-out and any explicit devId are both reversible. TEST_DEVID is + * unregistered first, leaving the probe as wolfCrypt's own selection. */ + wolfPSA_UnRegisterCryptoCb(TEST_DEVID); + + if (ret == 0 && wolfPSA_SetDefaultDevID(WOLFPSA_DEVID_DEFAULT) != 0) { + printf("FAIL wolfPSA_SetDefaultDevID(WOLFPSA_DEVID_DEFAULT)\n"); + ret = 1; + } + + if (ret == 0 && wolfPSA_GetDefaultDevID() != PROBE_DEVID) { + printf("FAIL default was not handed back to wolfCrypt (got %d," + " want %d)\n", wolfPSA_GetDefaultDevID(), PROBE_DEVID); + ret = 1; + } + + if (ret == 0) { + printf("handed back to wolfCrypt (devId %d):\n", PROBE_DEVID); + reset_counts(PROBE_DEVID); + ret = exercise_all(); + } + + if (ret == 0) { + ret = check_families(); + } + + wolfPSA_UnRegisterCryptoCb(PROBE_DEVID); if (ret != 0) return 1; diff --git a/wolfpsa.map b/wolfpsa.map index e389b90..cf94cbf 100644 --- a/wolfpsa.map +++ b/wolfpsa.map @@ -134,6 +134,14 @@ WOLFPSA_1.0 { wolfpsa_test_set_next_key_id; wolfPSA_SetDefaultDevID; wolfPSA_GetDefaultDevID; + /* The wolfCrypt objects a devId selects live inside this library, + * so a shared-library user has to register the device against this + * copy of the callback table rather than their own wolfSSL. The raw + * wc_CryptoCb_* names are deliberately not exported: an application + * that also links libwolfssl would bind them by link order, and + * silently register into the wrong table. */ + wolfPSA_RegisterCryptoCb; + wolfPSA_UnRegisterCryptoCb; local: *; }; diff --git a/wolfpsa/psa_engine.h b/wolfpsa/psa_engine.h index daa78a8..82f2a0d 100644 --- a/wolfpsa/psa_engine.h +++ b/wolfpsa/psa_engine.h @@ -47,63 +47,103 @@ #include #endif +#include + #ifdef __cplusplus extern "C" { #endif +/* Pass this to wolfPSA_SetDefaultDevID() to hand the choice back to + * wolfCrypt, which is where wolfPSA starts. Reserved by convention: + * wc_CryptoCb_RegisterDevice() rejects only INVALID_DEVID, so a device + * registered at INT_MIN is accepted by wolfCrypt but unreachable here. + * Distinct from INVALID_DEVID, which forces local execution. */ +#define WOLFPSA_DEVID_DEFAULT INT_MIN + /* wolfCrypt error code to PSA status code conversion */ WOLFSSL_LOCAL psa_status_t wc_error_to_psa_status(int ret); /* Default wolfCrypt devId threaded through wolfPSA's internal wc_*Init() - * calls. Set it to a registered crypto_cb devId (e.g. via - * wc_CryptoCb_RegisterDevice) to route every wolfPSA-issued wolfCrypt call - * through that callback, which is the integration hook for crypto offload - * backends such as wolfHSM or a hardware accelerator. Safe to call before - * psa_crypto_init(). + * calls. Set it to a registered devId to route every wolfPSA-issued + * wolfCrypt call through that callback, the integration hook for offload + * backends such as wolfHSM. Safe to call before psa_crypto_init(). + * + * Unset, wolfCrypt's own selection applies (wc_CryptoCb_DefaultDevID()). + * An explicit devId wins over it, INVALID_DEVID included: that one forces + * local execution. Restore with WOLFPSA_DEVID_DEFAULT, never with a value + * read back from wolfPSA_GetDefaultDevID(), which resolves the deferred + * state rather than reporting it and would turn "let wolfCrypt choose" + * into a hard pin. + * + * Covers every algorithm whose wolfCrypt initializer accepts a devId: AES, + * 3DES, RSA, ECC, Ed25519, Ed448, X25519, X448, ML-DSA, ML-KEM, LMS, XMSS, + * SHA-1, the SHA-2 and SHA-3 families, SHAKE, HMAC, CMAC, HKDF, PBKDF2 and + * the RNG. RIPEMD-160, MD5, Ascon and ChaCha20-Poly1305 always run + * locally, because wolfCrypt exposes no devId for them or ignores the one + * it accepts. AES key wrap does too for now: wc_AesKeyWrap_ex() falls back + * to software when the device has no keywrap handler, and a device that + * took the KEK through WOLF_CRYPTO_CB_AES_SETKEY leaves no key schedule + * for that fallback to run on. * - * Until this is called wolfPSA expresses no preference and wolfCrypt's own - * device selection applies (wc_CryptoCb_DefaultDevID(), so - * WOLFSSL_CAAM_DEVID, WC_USE_DEVID or the first registered device, unless - * the build sets WC_NO_DEFAULT_DEVID). Any explicit devId wins over that - * selection, INVALID_DEVID included: passing it forces every wolfPSA - * operation to run locally even when other devices are registered. + * A backend that keeps per-operation state in devCtx must be built with + * WOLF_CRYPTO_CB_COPY and WOLF_CRYPTO_CB_FREE, or psa_hash_clone() + * duplicates the handle and psa_hash_abort() never releases it. * - * Coverage: every algorithm whose wolfCrypt init function accepts a devId, - * which is AES, 3DES, RSA, ECC, Ed25519, Ed448, X25519, ML-DSA, - * ML-KEM, LMS, XMSS, SHA-1, the SHA-2 and SHA-3 families, SHAKE, HMAC, - * CMAC, HKDF, PBKDF2 and the RNG. X448, RIPEMD-160, MD5, Ascon and - * ChaCha20-Poly1305 always run locally, either because wolfCrypt exposes no - * devId for them or because it accepts one and ignores it. AES key wrap - * (PSA_ALG_KW) also stays local for now: wc_AesKeyWrap_ex() falls back to - * software when the device has no keywrap handler, and a device that - * installed the KEK through WOLF_CRYPTO_CB_AES_SETKEY leaves no software - * key schedule for that fallback to use, so the wrap would silently run - * under all-zero round keys. It joins the list once wolfCrypt refuses that - * fallback. + * PSA_ALG_DETERMINISTIC_ECDSA never offloads: the callback contract has no + * deterministic flag, so wolfPSA pins that one algorithm to software. On a + * WOLF_CRYPTO_CB_ONLY_ECC build there is no software to pin to and it + * reports PSA_ERROR_NOT_SUPPORTED. * - * A callback that does not implement a given algorithm returns - * CRYPTOCB_UNAVAILABLE and wolfCrypt falls back to software. One case needs - * more than that: a backend that keeps per-operation state in devCtx should - * be built with WOLF_CRYPTO_CB_COPY and WOLF_CRYPTO_CB_FREE, otherwise - * psa_hash_clone() duplicates the handle and psa_hash_abort() never - * releases it. + * Two escapes outside wolfPSA's control: deriving an X25519 public key + * goes through the keyless wc_CryptoCb_Curve25519MakePub() and so reaches + * whichever device registered first (keygen, agreement and all of + * Curve448 are unaffected), and a WOLF_CRYPTO_CB_FIND build consults its + * find callback regardless of devId. * - * Threading: the default devId is held in a process-global variable read - * by every wolfPSA-internal wc_*Init() invocation. Callers must set it - * during single-threaded initialisation (before any PSA operation is - * issued) or otherwise serialise the setter with external synchronisation; - * concurrent calls to wolfPSA_SetDefaultDevID() while PSA operations are - * in flight are not supported. + * Threading: one atomic, so setting it under concurrent PSA traffic is + * defined behaviour wherever wolfSSL_Atomic_Int is real; SINGLE_THREADED + * and WOLFSSL_NO_ATOMICS builds get a plain int and must serialize the + * setter themselves. Not a barrier: the value is read once per wolfCrypt + * object, so an operation spanning the change can use one devId for part + * of its work and another for the rest. It covers this setting only, not + * wolfCrypt's unsynchronized gCryptoDev[] table, so register and + * unregister during single-threaded init or teardown. * - * Returns 0 on success. */ + * Returns 0, or NOT_COMPILED_IN for a real devId in a library built + * without WOLF_CRYPTO_CB. Speaks wolfCrypt, not PSA: int devId and + * error-crypt.h codes, not psa_status_t. */ WOLFSSL_API int wolfPSA_SetDefaultDevID(int devId); /* Returns the devId wolfPSA passes to wolfCrypt: the value given to - * wolfPSA_SetDefaultDevID(), or wolfCrypt's own selection when that was - * never called. It is therefore not a test for whether a devId was - * configured, and it can name a device nobody asked wolfPSA to use. */ + * wolfPSA_SetDefaultDevID(), or wolfCrypt's own selection while the + * setting is WOLFPSA_DEVID_DEFAULT. It is therefore not a test for whether + * a devId was configured, and it can name a device nobody asked wolfPSA to + * use. It never returns WOLFPSA_DEVID_DEFAULT itself. */ WOLFSSL_API int wolfPSA_GetDefaultDevID(void); +/* wolfCrypt's CryptoDevCallbackFunc sits inside cryptocb.h's WOLF_CRYPTO_CB + * guard, while the two entry points below stay in the ABI for every + * configuration. Alias it where it exists so the two cannot drift apart. */ +#ifdef WOLF_CRYPTO_CB + #include + typedef CryptoDevCallbackFunc wolfPSA_CryptoCbFunc; +#else + struct wc_CryptoInfo; + typedef int (*wolfPSA_CryptoCbFunc)(int devId, struct wc_CryptoInfo *info, + void *ctx); +#endif + +/* Register or unregister a crypto callback against the device table + * wolfPSA dispatches through. Use these rather than + * wc_CryptoCb_RegisterDevice() directly: libwolfpsa links its own copy of + * wolfCrypt, so an application that also links libwolfssl has two tables + * and a bare call binds by link order. Returns 0, or NOT_COMPILED_IN + * without WOLF_CRYPTO_CB. Call during single-threaded init or teardown, + * per the threading note above. */ +WOLFSSL_API int wolfPSA_RegisterCryptoCb(int devId, wolfPSA_CryptoCbFunc cb, + void *ctx); +WOLFSSL_API int wolfPSA_UnRegisterCryptoCb(int devId); + #ifdef __cplusplus } #endif From 3fe3876bd7cf5f56915bbddbb30db780252ae9a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 1 Sep 2026 15:12:25 +0200 Subject: [PATCH 3/3] cipher: reject a NULL input in psa_cipher_decrypt psa_cipher_decrypt() copies the IV prefix out of the input with XMEMCPY() before handing the remainder to psa_cipher_update(), so it never reaches that function's NULL-input guard and dereferenced a NULL input directly. psa_cipher_encrypt() only ever touches the input through psa_cipher_update() and was already safe. Reject a NULL input with a non-zero length in psa_cipher_decrypt() and cover both entry points in psa_cipher_oneshot_len_test. --- src/psa_cipher.c | 7 +++++++ test/psa_server/psa_cipher_oneshot_len_test.c | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/psa_cipher.c b/src/psa_cipher.c index 0725a5c..8a9ace3 100644 --- a/src/psa_cipher.c +++ b/src/psa_cipher.c @@ -1628,6 +1628,13 @@ psa_status_t psa_cipher_decrypt(psa_key_id_t key, if (output == NULL && output_size > 0) { return PSA_ERROR_INVALID_ARGUMENT; } + /* Unlike psa_cipher_encrypt(), the decrypt path consumes the IV + * prefix with XMEMCPY() before it hands the remainder to + * psa_cipher_update(), so it cannot rely on that function's own + * NULL-input guard and has to reject a NULL input here. */ + if (input == NULL && input_length > 0) { + return PSA_ERROR_INVALID_ARGUMENT; + } /* Mirror the one-shot encrypt contract: any overlap between the * declared input and output ranges is rejected. In the block modes diff --git a/test/psa_server/psa_cipher_oneshot_len_test.c b/test/psa_server/psa_cipher_oneshot_len_test.c index 1758724..90ce96c 100644 --- a/test/psa_server/psa_cipher_oneshot_len_test.c +++ b/test/psa_server/psa_cipher_oneshot_len_test.c @@ -104,6 +104,25 @@ int main(void) rc = 1; } + /* NULL input with a non-zero input_length must be rejected too. + * psa_cipher_encrypt() only ever touches the input through + * psa_cipher_update(), which guards it; psa_cipher_decrypt() + * copies the IV prefix out of the input first, so without its own + * guard this call dereferenced NULL. */ + st = psa_cipher_encrypt(key_id, PSA_ALG_CBC_NO_PADDING, NULL, + sizeof(ct_in), ct, sizeof(ct), &ct_len); + if (st != PSA_ERROR_INVALID_ARGUMENT) { + printf("FAIL encrypt NULL input: status=%d\n", (int)st); + rc = 1; + } + + st = psa_cipher_decrypt(key_id, PSA_ALG_CBC_NO_PADDING, NULL, + sizeof(ct_in), pt, sizeof(pt), &pt_len); + if (st != PSA_ERROR_INVALID_ARGUMENT) { + printf("FAIL decrypt NULL input: status=%d\n", (int)st); + rc = 1; + } + /* Controls: a normal one-shot roundtrip still works. */ st = psa_cipher_encrypt(key_id, PSA_ALG_CBC_NO_PADDING, plain, 16, ct, sizeof(ct), &ct_len);