diff --git a/.github/workflows/test-psa-api.yml b/.github/workflows/test-psa-api.yml index 15cf56d..f0c4143 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,8 +162,18 @@ 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 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/.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..721801e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,20 @@ 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, 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/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_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/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..87631dc 100644 --- a/src/psa_engine.c +++ b/src/psa_engine.c @@ -31,22 +31,79 @@ #include #include #include +#include #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. */ -static int wolfPSA_default_devid = INVALID_DEVID; + * 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; +#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) { - return wolfPSA_default_devid; + int devId = (int)WOLFSSL_ATOMIC_LOAD(wolfPSA_default_devid); + +#ifdef WOLF_CRYPTO_CB + /* 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 } /* wolfCrypt error code to PSA status code conversion */ 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..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,18 +311,44 @@ 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; } - ret = wc_HmacSetKey(&ctx->ctx.hmac, hash_type, key_data, - (word32)key_data_length); 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 + } } #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 @@ -555,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_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..808beb4 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); @@ -283,17 +295,24 @@ 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); } - 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); } 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); @@ -323,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)) || @@ -341,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) { @@ -397,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/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..a786038 100644 --- a/test/README.md +++ b/test/README.md @@ -13,6 +13,19 @@ 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()`, 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. 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` - TLS server built with wolfPSA, and a static client built from wolfSSL sources (`psa_server/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_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); 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..58ff5fc --- /dev/null +++ b/test/psa_server/psa_devid_cryptocb_test.c @@ -0,0 +1,951 @@ +/* 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"; + } +} + +/* 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. + * + * 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; + + 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 (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) { + /* 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 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) +{ + 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 */ + +#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) +{ + 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 +#ifdef HAVE_CURVE448 + ret |= exercise_x448(); +#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(), _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 + * 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 +#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; +} + +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. */ + /* 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; + } + + 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 && wolfPSA_RegisterCryptoCb(TEST_DEVID, count_cb, + NULL) != 0) { + printf("FAIL wolfPSA_RegisterCryptoCb(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(); + } + +#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(); + } + 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"); + } + + /* 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; + + printf("PSA devId crypto callback test: OK\n"); + return 0; +} 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 28e33eb..82f2a0d 100644 --- a/wolfpsa/psa_engine.h +++ b/wolfpsa/psa_engine.h @@ -47,34 +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. 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 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. + * + * 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. * - * 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. + * 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. * - * Returns 0 on success. */ + * 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: 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, 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 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 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