Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion .github/workflows/test-psa-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 9 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -86,6 +87,7 @@ ifneq ($(strip $(PSA_INCLUDE)),)
CPPFLAGS += -I$(PSA_INCLUDE)
endif

DEPFLAGS := -MMD -MP
CFLAGS ?= -O2
WARNFLAGS ?= -Wall -Wextra -Werror
CFLAGS += $(WARNFLAGS)
Expand Down Expand Up @@ -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)
4 changes: 2 additions & 2 deletions src/psa_asymmetric_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 7 additions & 0 deletions src/psa_cipher.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 39 additions & 7 deletions src/psa_ecc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down
40 changes: 30 additions & 10 deletions src/psa_ed25519_ed448.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Comment thread
Frauschi marked this conversation as resolved.
Expand Down Expand Up @@ -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);
}
Expand Down
Loading
Loading