Skip to content
Open
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
70 changes: 70 additions & 0 deletions .github/workflows/se050-sim.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: SE050 simulator test

# START OF COMMON SECTION
on:
push:
branches: [ 'master', 'main', 'release/**' ]
pull_request:
branches: [ '*' ]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# END OF COMMON SECTION

# Build the SE050 software simulator (https://github.com/LinuxJedi/SE050Sim),
# build wolfSSL against its NXP Plug&Trust SDK + simulator bridge, and run the
# wolfCrypt SE050 test binary against the simulator TCP server.
#
# The simulator's own Dockerfile (Dockerfile.wolfcrypt) clones wolfSSL master.
# We patch it to COPY the PR checkout instead so CI reflects the PR's source.

env:
# Pin the simulator to a known-good revision. Bump this deliberately after
# validating upstream changes in a standalone PR.
SE050SIM_REF: main
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow comment says the simulator is pinned to a "known-good revision", but SE050SIM_REF is set to main, which is a moving target and can make CI non-reproducible. Pin this to an immutable ref (tag or commit SHA), and update it intentionally when validating upstream changes.

Suggested change
SE050SIM_REF: main
SE050SIM_REF: "<FULL_40_CHAR_VALIDATED_SE050SIM_COMMIT_SHA>"

Copilot uses AI. Check for mistakes.

jobs:
se050_sim:
name: wolfCrypt against SE050 simulator
if: github.repository_owner == 'wolfssl'
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- name: Checkout wolfSSL (PR source)
uses: actions/checkout@v4
with:
path: wolfssl-src

- name: Clone SE050 simulator
run: |
git clone https://github.com/LinuxJedi/SE050Sim se050sim
cd se050sim && git checkout "$SE050SIM_REF"

- name: Stage PR wolfSSL into simulator build context
run: mv wolfssl-src se050sim/wolfssl

- name: Patch Dockerfile to use PR wolfSSL instead of upstream master
working-directory: se050sim
run: |
sed -i 's|^RUN git clone --depth 1 https://github.com/wolfSSL/wolfssl.git /app/wolfssl$|COPY wolfssl /app/wolfssl|' Dockerfile.wolfcrypt
# Fail fast if the pattern drifted upstream — better a clear error
# than a CI run that silently tests master.
grep -q '^COPY wolfssl /app/wolfssl$' Dockerfile.wolfcrypt
! grep -q 'git clone .*wolfssl\.git' Dockerfile.wolfcrypt

- uses: docker/setup-buildx-action@v3

- name: Build wolfCrypt-SE050 test image
uses: docker/build-push-action@v5
with:
context: se050sim
file: se050sim/Dockerfile.wolfcrypt
push: false
load: true
tags: wolfssl-se050-sim:ci
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Run wolfCrypt tests against simulator
run: docker run --rm wolfssl-se050-sim:ci
18 changes: 18 additions & 0 deletions wolfcrypt/src/ed25519.c
Original file line number Diff line number Diff line change
Expand Up @@ -1169,6 +1169,12 @@ int wc_ed25519_import_public_ex(const byte* in, word32 inLen, ed25519_key* key,
if (inLen < ED25519_PUB_KEY_SIZE)
return BAD_FUNC_ARG;

#ifdef WOLFSSL_SE050
/* Importing new key material invalidates any prior SE050 object binding. */
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the SE050 build, clearing key->keyIdSet/key->keyId on public-key import drops the binding but does not erase the previously-uploaded SE050 object. Because se050_ed25519_free_key() only erases when keyIdSet==1, this change can leak transient key objects (and leave stale key material) when reusing an ed25519_key for multiple imports. Consider calling se050_ed25519_free_key(key) (or otherwise erasing the old object when keyIdSet==1) before resetting these fields.

Suggested change
/* Importing new key material invalidates any prior SE050 object binding. */
/* Importing new key material invalidates any prior SE050 object binding. */
if (key->keyIdSet == 1) {
se050_ed25519_free_key(key);
}

Copilot uses AI. Check for mistakes.
key->keyIdSet = 0;
key->keyId = 0;
#endif

/* compressed prefix according to draft
http://www.ietf.org/id/draft-koch-eddsa-for-openpgp-02.txt */
if (in[0] == 0x40 && inLen == ED25519_PUB_KEY_SIZE + 1) {
Expand Down Expand Up @@ -1255,6 +1261,12 @@ int wc_ed25519_import_private_only(const byte* priv, word32 privSz,
if (privSz != ED25519_KEY_SIZE)
return BAD_FUNC_ARG;

#ifdef WOLFSSL_SE050
/* Importing new key material invalidates any prior SE050 object binding. */
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the SE050 build, clearing key->keyIdSet/key->keyId on private-only import drops the binding but does not erase the previously-uploaded SE050 object. Because se050_ed25519_free_key() only erases when keyIdSet==1, this can leak transient key objects (and leave stale key material) when reusing an ed25519_key for multiple imports. Consider calling se050_ed25519_free_key(key) (or otherwise erasing the old object when keyIdSet==1) before resetting these fields.

Suggested change
/* Importing new key material invalidates any prior SE050 object binding. */
/* Importing new key material invalidates any prior SE050 object binding. */
if (key->keyIdSet) {
se050_ed25519_free_key(key);
}

Copilot uses AI. Check for mistakes.
key->keyIdSet = 0;
key->keyId = 0;
#endif

XMEMCPY(key->k, priv, ED25519_KEY_SIZE);
key->privKeySet = 1;

Expand Down Expand Up @@ -1311,6 +1323,12 @@ int wc_ed25519_import_private_key_ex(const byte* priv, word32 privSz,
return BAD_FUNC_ARG;
}

#ifdef WOLFSSL_SE050
/* Importing new key material invalidates any prior SE050 object binding. */
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the SE050 build, clearing key->keyIdSet/key->keyId on private+public import drops the binding but does not erase the previously-uploaded SE050 object. Because se050_ed25519_free_key() only erases when keyIdSet==1, this can leak transient key objects (and leave stale key material) when reusing an ed25519_key for multiple imports. Consider calling se050_ed25519_free_key(key) (or otherwise erasing the old object when keyIdSet==1) before resetting these fields.

Suggested change
/* Importing new key material invalidates any prior SE050 object binding. */
/* Importing new key material invalidates any prior SE050 object binding.
* If this key was previously uploaded to SE050, erase that object before
* dropping the binding metadata.
*/
if (key->keyIdSet == 1) {
ret = se050_ed25519_free_key(key);
if (ret != 0) {
return ret;
}
}

Copilot uses AI. Check for mistakes.
key->keyIdSet = 0;
key->keyId = 0;
#endif

XMEMCPY(key->k, priv, ED25519_KEY_SIZE);
key->privKeySet = 1;

Expand Down
20 changes: 18 additions & 2 deletions wolfcrypt/src/port/nxp/se050_port.c
Original file line number Diff line number Diff line change
Expand Up @@ -1538,8 +1538,22 @@ int se050_rsa_verify(const byte* in, word32 inLen, byte* out, word32 outLen,
}

if (status == kStatus_SSS_Success) {
key->keyId = keyId;
key->keyIdSet = 1;
if (keyCreated) {
/* We uploaded only the public part of the key for this verify.
* Don't persist keyIdSet=1 — a later sign on the same RsaKey
* would reuse this binding and fail because the SE050 object has
* no private material. Erase the transient object so the next
* SE050 op (sign or verify) re-uploads from whatever the host
* RsaKey currently holds. */
sss_key_store_erase_key(&host_keystore, &newKey);
sss_key_object_free(&newKey);
}
else {
/* Pre-existing keyIdSet=1 binding (e.g. wc_RsaUseKeyId or prior
* sign that uploaded a keypair). Preserve it. */
key->keyId = keyId;
key->keyIdSet = 1;
}
}
else {
if (keyCreated) {
Expand Down Expand Up @@ -3039,6 +3053,8 @@ int se050_ed25519_verify_msg(const byte* signature, word32 signatureLen,
key, signature, signatureLen, msg, msgLen);
#endif

*res = 0;

Comment on lines 3055 to +3057
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

se050_ed25519_verify_msg() now unconditionally writes *res = 0; before any argument validation. If the caller passes res == NULL this will segfault (and the SE050 path in wc_ed25519_verify_msg_ex() currently skips the host-side sanity checks). Add an explicit res != NULL check before dereferencing (and ideally mirror the host-side NULL checks for signature/msg/key on the SE050 path).

Suggested change
*res = 0;
if (res == NULL) {
return BAD_FUNC_ARG;
}
*res = 0;
if (signature == NULL || msg == NULL || key == NULL) {
return BAD_FUNC_ARG;
}

Copilot uses AI. Check for mistakes.
if (cfg_se050_i2c_pi == NULL) {
return WC_HW_E;
}
Expand Down
61 changes: 52 additions & 9 deletions wolfcrypt/test/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -25012,7 +25012,7 @@ static wc_test_ret_t rsa_keygen_test(WC_RNG* rng)
#else
byte der[1280];
#endif
#ifndef WOLFSSL_CRYPTOCELL
#if !defined(WOLFSSL_CRYPTOCELL) && !defined(WOLFSSL_SE050)
word32 idx = 0;
#endif
int derSz = 0;
Expand Down Expand Up @@ -25089,13 +25089,16 @@ static wc_test_ret_t rsa_keygen_test(WC_RNG* rng)
if (ret != 0)
ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_rsa);

#ifndef WOLFSSL_CRYPTOCELL
#if !defined(WOLFSSL_CRYPTOCELL) && !defined(WOLFSSL_SE050)
idx = 0;
/* The private key part of the key gen pairs from cryptocell can't be exported */
/* The private key part of key pairs generated inside a secure element
* (CryptoCell, SE050) stays in hardware and isn't available to
* wc_RsaKeyToDer, so the exported DER can't be parsed back as a
* complete RSAPrivateKey. */
ret = wc_RsaPrivateKeyDecode(der, &idx, genKey, (word32)derSz);
if (ret != 0)
ERROR_OUT(WC_TEST_RET_ENC_EC(ret), exit_rsa);
#endif /* WOLFSSL_CRYPTOCELL */
#endif /* !WOLFSSL_CRYPTOCELL && !WOLFSSL_SE050 */

exit_rsa:

Expand Down Expand Up @@ -40971,7 +40974,16 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t ed25519_test(void)

#if defined(HAVE_ED25519_SIGN) && defined(HAVE_ED25519_KEY_EXPORT) && \
defined(HAVE_ED25519_KEY_IMPORT)
#ifdef WOLFSSL_SE050
/* Iter 5 uses RFC 8032 msg4 (~1023 bytes), which exceeds the NXP
* Plug&Trust SDK's SE05X_TLV_BUF_SIZE_CMD = 900 byte APDU buffer:
* EdDSASign fails with "Not enough buffer" before the command reaches
* the secure element. Cap at 5 iterations until the SDK buffer is
* enlarged upstream. */
for (i = 0; i < 5; i++) {
#else
for (i = 0; i < 6; i++) {
#endif
outlen = sizeof(out);
XMEMSET(out, 0, sizeof(out));

Expand Down Expand Up @@ -41044,7 +41056,12 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t ed25519_test(void)
#endif /* HAVE_ED25519_VERIFY */
}

#ifdef HAVE_ED25519_VERIFY
#if defined(HAVE_ED25519_VERIFY)
/* These cases exercise host-side signature-encoding pre-validation (e.g.,
* sig == curve order). The SE050 port delegates verify to the secure
* element, which rejects all four inputs with WC_HW_E rather than the
* BAD_FUNC_ARG / SIG_VERIFY_E the host-side path produces — so the
* expected error code differs below when built against an SE050. */
{
/* Run tests for some rare code paths */
/* sig is exactly equal to the order */
Expand Down Expand Up @@ -41097,35 +41114,53 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t ed25519_test(void)
if (ret != 0)
ERROR_OUT(WC_TEST_RET_ENC_EC(ret), cleanup);

#ifdef WOLFSSL_SE050
#define RARE_ED_BAD_ENC_E WC_HW_E
#define RARE_ED_BAD_SIG_E WC_HW_E
#else
#define RARE_ED_BAD_ENC_E BAD_FUNC_ARG
#define RARE_ED_BAD_SIG_E SIG_VERIFY_E
#endif

ret = wc_ed25519_verify_msg(rareEd1, sizeof(rareEd1), msgs[0], msgSz[0],
&verify, key);
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG))
if (ret != WC_NO_ERR_TRACE(RARE_ED_BAD_ENC_E))
ERROR_OUT(WC_TEST_RET_ENC_EC(ret), cleanup);

ret = wc_ed25519_verify_msg(rareEd2, sizeof(rareEd2), msgs[0], msgSz[0],
&verify, key);
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG))
if (ret != WC_NO_ERR_TRACE(RARE_ED_BAD_ENC_E))
ERROR_OUT(WC_TEST_RET_ENC_EC(ret), cleanup);

ret = wc_ed25519_verify_msg(rareEd3, sizeof(rareEd3), msgs[0], msgSz[0],
&verify, key);
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG))
if (ret != WC_NO_ERR_TRACE(RARE_ED_BAD_ENC_E))
ERROR_OUT(WC_TEST_RET_ENC_EC(ret), cleanup);

ret = wc_ed25519_verify_msg(rareEd4, sizeof(rareEd4), msgs[0], msgSz[0],
&verify, key);
if (ret != WC_NO_ERR_TRACE(SIG_VERIFY_E))
if (ret != WC_NO_ERR_TRACE(RARE_ED_BAD_SIG_E))
ERROR_OUT(WC_TEST_RET_ENC_EC(ret), cleanup);

#undef RARE_ED_BAD_ENC_E
#undef RARE_ED_BAD_SIG_E
}
#endif /* HAVE_ED25519_VERIFY */

#ifndef WOLFSSL_SE050
/* Ed25519ctx and Ed25519ph require passing a context / prehash flag
* through to the signer. The SE050 port's se050_ed25519_sign_msg /
* _verify_msg drop those parameters and always do plain Ed25519, so the
* RFC 8032 ctx/ph test vectors cannot match. Skip these variants when
* built against an SE050. */
ret = ed25519ctx_test();
if (ret != 0)
goto cleanup;

ret = ed25519ph_test();
if (ret != 0)
goto cleanup;
#endif /* !WOLFSSL_SE050 */

#ifndef NO_ASN
/* Try ASN.1 encoded private-only key and public key. */
Expand All @@ -41140,8 +41175,16 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t ed25519_test(void)
sizeof(badPrivateEd25519)) == 0)
ERROR_OUT(WC_TEST_RET_ENC_NC, cleanup);

/* Signing with a private-only key (no public loaded yet) is rejected on
* the host with BAD_FUNC_ARG. The SE050 port instead fails inside
* sss_se05x_key_store_set_ecc_keypair and returns WC_HW_E, so accept
* that alternate error code when built against an SE050. */
ret = wc_ed25519_sign_msg(msgs[0], msgSz[0], out, &outlen, key3);
#ifdef WOLFSSL_SE050
if (ret != WC_NO_ERR_TRACE(WC_HW_E))
#else
if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG))
#endif
ERROR_OUT(WC_TEST_RET_ENC_EC(ret), cleanup);

/* try with a buffer size that is too large */
Expand Down
Loading