Skip to content
7 changes: 5 additions & 2 deletions .github/workflows/cryptocb-only.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
name: cryptocb-only Tests

# START OF COMMON SECTION
Expand Down Expand Up @@ -105,9 +105,12 @@
{"name": "aes-gcm-via-ecb",
"comment": "Same as aes but tells swdev to refuse AES-GCM (SWDEV_AES_ONLYECB). That forces the parent's CB_ONLY_AES host-side GCM software path: GHASH runs on the host while AES-CTR blocks dispatch back through cryptocb ECB. The aes entry instead has swdev handle GCM end-to-end, so the host-side GCM path is otherwise uncovered.",
"configure": ["CPPFLAGS=-DWOLF_CRYPTO_CB_ONLY_AES -DSWDEV_AES_ONLYECB"]},
{"name": "ed25519",
"comment": "WOLF_CRYPTO_CB_ONLY_ED25519: strips software Ed25519 (keygen/sign/verify/make-pub/check-key) including the ge/fe curve math and tables; swdev provides the software path via cryptocb. Streaming verify has no callback path and is left disabled.",
"configure": ["CPPFLAGS=-DWOLF_CRYPTO_CB_ONLY_ED25519"]},
{"name": "all",
"comment": "All five ONLY_* macros at once: every supported software primitive is stripped and dispatched through cryptocb. Catches any cross-algorithm call that a single-strip entry would still resolve via the remaining software paths.",
"configure": ["CPPFLAGS=-DWOLF_CRYPTO_CB_ONLY_ECC -DWOLF_CRYPTO_CB_ONLY_RSA -DWOLF_CRYPTO_CB_ONLY_SHA256 -DWOLF_CRYPTO_CB_ONLY_SHA512 -DWOLF_CRYPTO_CB_ONLY_AES"]}
"comment": "All six ONLY_* macros at once: every supported software primitive is stripped and dispatched through cryptocb. Catches any cross-algorithm call that a single-strip entry would still resolve via the remaining software paths.",
"configure": ["CPPFLAGS=-DWOLF_CRYPTO_CB_ONLY_ECC -DWOLF_CRYPTO_CB_ONLY_RSA -DWOLF_CRYPTO_CB_ONLY_SHA256 -DWOLF_CRYPTO_CB_ONLY_SHA512 -DWOLF_CRYPTO_CB_ONLY_AES -DWOLF_CRYPTO_CB_ONLY_ED25519"]}
]}
EOF
.github/scripts/parallel-make-check.py \
Expand Down
52 changes: 52 additions & 0 deletions doc/dox_comments/header_files/cryptocb.h
Original file line number Diff line number Diff line change
Expand Up @@ -341,3 +341,55 @@ int wc_CryptoCb_EccMakePub(ecc_key* key, ecc_point* pubOut);
\sa wc_ecc_check_key
*/
int wc_CryptoCb_EccCheckPubKey(ecc_key* key, int checkOrder, int checkPriv);

/*!
\ingroup CryptoCb

\brief Offload deriving an Ed25519 public key from its private key to a
CryptoCB device.

Used by wc_ed25519_make_public (and so by key generation and private-key
import). The boundary is byte-oriented: the device writes the compressed
public key into wc_CryptoInfo.pk.ed25519makepub (\c pubOut /
\c pubOutSz, always ED25519_PUB_KEY_SIZE). The private key is taken from
the ed25519_key (resident in a secure element, or key->k). On success the
caller marks the public key set.

\param key Ed25519 key providing the device id, heap hint and the
private key
\param pubKey [out] resulting compressed public key
\param pubKeySz size of pubKey buffer, must be ED25519_PUB_KEY_SIZE

\return 0 on success
\return CRYPTOCB_UNAVAILABLE if no device handles the operation, or key or
pubKey is NULL or pubKeySz is wrong (wolfCrypt falls back to
software, which reports the argument error)

\sa wc_CryptoCb_RegisterDevice
\sa wc_ed25519_make_public
*/
int wc_CryptoCb_Ed25519MakePub(ed25519_key* key, byte* pubKey,
word32 pubKeySz);

/*!
\ingroup CryptoCb

\brief Offload validating an Ed25519 key to a CryptoCB device.

Used by wc_ed25519_check_key and the key import validation paths. The
public key crosses the callback boundary as its compressed wire bytes in
wc_CryptoInfo.pk.ed25519checkkey (\c pubKey / \c pubKeySz), so a device
handler only deals with byte arrays. The dispatch only runs when a public
key is present; \c checkPriv is 1 when a private key is also set and the
device should additionally validate priv/pub consistency.

\param key Ed25519 key to validate

\return 0 if the key is valid
\return CRYPTOCB_UNAVAILABLE if no device handles the operation (wolfCrypt
falls back to software)

\sa wc_CryptoCb_RegisterDevice
\sa wc_ed25519_check_key
*/
int wc_CryptoCb_Ed25519CheckKey(ed25519_key* key);
6 changes: 4 additions & 2 deletions tests/api.c
Original file line number Diff line number Diff line change
Expand Up @@ -30047,7 +30047,8 @@ static int test_SSL_CIPHER_get_current_kx(void)
#if defined(WOLF_CRYPTO_CB) && defined(HAVE_IO_TESTS_DEPENDENCIES) && \
(!defined(WOLF_CRYPTO_CB_ONLY_SHA256) && !defined(WOLF_CRYPTO_CB_ONLY_AES) && \
!defined(WOLF_CRYPTO_CB_ONLY_ECC) && !defined(WOLF_CRYPTO_CB_ONLY_RSA) && \
!defined(WOLF_CRYPTO_CB_ONLY_SHA512))
!defined(WOLF_CRYPTO_CB_ONLY_SHA512) && \
!defined(WOLF_CRYPTO_CB_ONLY_ED25519))

static int load_pem_key_file_as_der(const char* privKeyFile, DerBuffer** pDer,
int* keyFormat)
Expand Down Expand Up @@ -31151,7 +31152,8 @@ static int test_wc_CryptoCb(void)
#if defined(WOLF_CRYPTO_CB) && \
(!defined(WOLF_CRYPTO_CB_ONLY_SHA256) && !defined(WOLF_CRYPTO_CB_ONLY_AES) && \
!defined(WOLF_CRYPTO_CB_ONLY_ECC) && !defined(WOLF_CRYPTO_CB_ONLY_RSA) && \
!defined(WOLF_CRYPTO_CB_ONLY_SHA512))
!defined(WOLF_CRYPTO_CB_ONLY_SHA512) && \
!defined(WOLF_CRYPTO_CB_ONLY_ED25519))
#if defined(HAVE_IO_TESTS_DEPENDENCIES) && \
(!defined(NO_RSA) || defined(HAVE_ECC) || defined(HAVE_ED25519))
int tlsVer;
Expand Down
12 changes: 7 additions & 5 deletions tests/swdev/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ compiled separately from the main library, linked into the test
programs only, and exposes exactly two C symbols. **It is not a
production component and must not be linked into shipping binaries.**

The four switches it supports are:
The switches it supports are:

| Macro | Strips | Test target |
|--------------------------------|----------------|--------------------|
| `WOLF_CRYPTO_CB_ONLY_RSA` | software RSA | RSA via CryptoCb |
| `WOLF_CRYPTO_CB_ONLY_ECC` | software ECC | ECC via CryptoCb |
| `WOLF_CRYPTO_CB_ONLY_SHA256` | software SHA-256 | SHA-256 via CryptoCb |
| `WOLF_CRYPTO_CB_ONLY_SHA512` | software SHA-512 | SHA-512 via CryptoCb |
| `WOLF_CRYPTO_CB_ONLY_AES` | software AES | AES via CryptoCb |
| `WOLF_CRYPTO_CB_ONLY_ED25519` | software Ed25519 | Ed25519 via CryptoCb |

When a test program calls e.g. `wc_AesCbcEncrypt()` against a libwolfssl
built with `-DWOLF_CRYPTO_CB_ONLY_AES`, the software AES path is gone;
Expand Down Expand Up @@ -55,7 +57,7 @@ internal copy of the AES code, and returns the result.
| wc_SwDev_Callback(devId, info, ctx) |
| - swdev_ensure_init() lazy wolfCrypt_Init |
| - switch (info->algo_type): |
| PK -> RSA / ECC software impl |
| PK -> RSA / ECC / Ed25519 software impl |
| HASH -> SHA-256 software impl |
| CIPHER -> AES (CBC/CTR/ECB/GCM/CCM) software impl |
| |
Expand All @@ -70,10 +72,10 @@ internal copy of the AES code, and returns the result.
The whole mechanism rests on compiling the wolfcrypt sources twice:

1. **libwolfssl** is built normally with the user's `_ONLY_*` flags
set, so its software RSA/ECC/SHA-256/AES paths are gone.
set, so its software RSA/ECC/SHA-256/SHA-512/AES/Ed25519 paths are gone.
2. **swdev** recompiles the same source set under
`tests/swdev/user_settings.h`, which `#undef`s all four `_ONLY_*`
macros. swdev therefore contains the full software implementations.
`tests/swdev/user_settings.h`, which `#undef`s every `_ONLY_*`
macro. swdev therefore contains the full software implementations.

To prevent symbol collisions when both are linked into the same test
binary, `tests/swdev/Makefile` does the following:
Expand Down
102 changes: 101 additions & 1 deletion tests/swdev/swdev.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
#ifndef NO_AES
#include <wolfssl/wolfcrypt/aes.h>
#endif
#ifdef HAVE_ED25519
#include <wolfssl/wolfcrypt/ed25519.h>
#endif

static int swdev_initialized = 0;

Expand Down Expand Up @@ -238,6 +241,83 @@ static int swdev_ecc_check_pub(wc_CryptoInfo* info)
#endif /* HAVE_ECC_CHECK_KEY */
#endif /* HAVE_ECC */

#ifdef HAVE_ED25519
#ifdef HAVE_ED25519_MAKE_KEY
static int swdev_ed25519_keygen(wc_CryptoInfo* info)
{
return wc_ed25519_make_key(info->pk.ed25519kg.rng,
info->pk.ed25519kg.size, info->pk.ed25519kg.key);
}
#endif

#ifdef HAVE_ED25519_SIGN
static int swdev_ed25519_sign(wc_CryptoInfo* info)
{
return wc_ed25519_sign_msg_ex(info->pk.ed25519sign.in,
info->pk.ed25519sign.inLen, info->pk.ed25519sign.out,
info->pk.ed25519sign.outLen, info->pk.ed25519sign.key,
info->pk.ed25519sign.type, info->pk.ed25519sign.context,
info->pk.ed25519sign.contextLen);
}
#endif

#ifdef HAVE_ED25519_VERIFY
static int swdev_ed25519_verify(wc_CryptoInfo* info)
{
return wc_ed25519_verify_msg_ex(info->pk.ed25519verify.sig,
info->pk.ed25519verify.sigLen, info->pk.ed25519verify.msg,
info->pk.ed25519verify.msgLen, info->pk.ed25519verify.res,
info->pk.ed25519verify.key, info->pk.ed25519verify.type,
info->pk.ed25519verify.context, info->pk.ed25519verify.contextLen);
}
#endif

#ifdef HAVE_ED25519_MAKE_KEY
static int swdev_ed25519_make_pub(wc_CryptoInfo* info)
{
if (info->pk.ed25519makepub.pubOutSz != ED25519_PUB_KEY_SIZE)
return BUFFER_E;
return wc_ed25519_make_public(info->pk.ed25519makepub.key,
info->pk.ed25519makepub.pubOut, info->pk.ed25519makepub.pubOutSz);
}
#endif

static int swdev_ed25519_check_key(wc_CryptoInfo* info)
{
ed25519_key* key = info->pk.ed25519checkkey.key;
int ret = 0;
int validatedFromWire = 0;

#ifdef HAVE_ED25519_KEY_IMPORT
{
WC_DECLARE_VAR(pubOnly, ed25519_key, 1, key->heap);

/* vault-style consumption: rebuild a public-only key from the wire
* bytes and validate it, proving the serialized form is sufficient.
* trusted=0 runs the full public-key validation during import. */
WC_ALLOC_VAR(pubOnly, ed25519_key, 1, key->heap);
if (!WC_VAR_OK(pubOnly))
return MEMORY_E;
ret = wc_ed25519_init_ex(pubOnly, key->heap, INVALID_DEVID);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 [Low] Resource leak when wc_ed25519_init_ex fails in swdev_ed25519_check_key · Resource leaks

wc_ed25519_free(pubOnly) is inside if (ret == 0), so if wc_ed25519_init_ex fails (e.g., under WOLFSSL_ED25519_PERSISTENT_SHA where ed25519_hash_init can fail), any partially allocated SHA state is not released before WC_FREE_VAR frees the struct.

Fix: Move wc_ed25519_free(pubOnly) outside the if (ret == 0) block so it always runs before WC_FREE_VAR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

if (ret == 0) {
ret = wc_ed25519_import_public_ex(info->pk.ed25519checkkey.pubKey,
info->pk.ed25519checkkey.pubKeySz, pubOnly, 0);
}
wc_ed25519_free(pubOnly);
WC_FREE_VAR(pubOnly, key->heap);
validatedFromWire = 1;
}
#endif
/* private part (and public-only validation when the import path is
* unavailable): validate via the key handle */
if (ret == 0 && (!validatedFromWire ||
info->pk.ed25519checkkey.checkPriv)) {
ret = wc_ed25519_check_key(key);
}
return ret;
}
#endif /* HAVE_ED25519 */

#ifndef NO_SHA256
/* Copy hash state between caller's wc_Sha256 and swdev's shadow, leaving
* admin fields (heap, devId, devCtx, W, async, HW ctx) per-side. */
Expand Down Expand Up @@ -833,7 +913,7 @@ WC_SWDEV_EXPORT int wc_SwDev_Callback(int devId, wc_CryptoInfo* info,
return ret;

switch (info->algo_type) {
#if !defined(NO_RSA) || defined(HAVE_ECC)
#if !defined(NO_RSA) || defined(HAVE_ECC) || defined(HAVE_ED25519)
case WC_ALGO_TYPE_PK:
switch (info->pk.type) {
#ifndef NO_RSA
Expand Down Expand Up @@ -864,6 +944,26 @@ WC_SWDEV_EXPORT int wc_SwDev_Callback(int devId, wc_CryptoInfo* info,
return swdev_ecc_check_pub(info);
#endif
#endif /* HAVE_ECC */
#ifdef HAVE_ED25519
#ifdef HAVE_ED25519_MAKE_KEY
case WC_PK_TYPE_ED25519_KEYGEN:
return swdev_ed25519_keygen(info);
#endif
#ifdef HAVE_ED25519_SIGN
case WC_PK_TYPE_ED25519_SIGN:
return swdev_ed25519_sign(info);
#endif
#ifdef HAVE_ED25519_VERIFY
case WC_PK_TYPE_ED25519_VERIFY:
return swdev_ed25519_verify(info);
#endif
#ifdef HAVE_ED25519_MAKE_KEY
case WC_PK_TYPE_ED25519_MAKE_PUB:
return swdev_ed25519_make_pub(info);
#endif
case WC_PK_TYPE_ED25519_CHECK_KEY:
return swdev_ed25519_check_key(info);
#endif /* HAVE_ED25519 */
default:
return CRYPTOCB_UNAVAILABLE;
}
Expand Down
1 change: 1 addition & 0 deletions tests/swdev/user_settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#undef WOLF_CRYPTO_CB_ONLY_SHA256
#undef WOLF_CRYPTO_CB_ONLY_SHA512
#undef WOLF_CRYPTO_CB_ONLY_AES
#undef WOLF_CRYPTO_CB_ONLY_ED25519

#ifndef WOLF_CRYPTO_CB
#error "wc_swdev requires the main build to define WOLF_CRYPTO_CB"
Expand Down
57 changes: 57 additions & 0 deletions wolfcrypt/src/cryptocb.c
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ Crypto Callback Build Options:
* WOLF_CRYPTO_CB_ONLY_SHA256: Use only callbacks for SHA-256 default: off
* WOLF_CRYPTO_CB_ONLY_SHA512: Use only callbacks for SHA-512 default: off
* WOLF_CRYPTO_CB_ONLY_AES: Use only callbacks for AES default: off
* WOLF_CRYPTO_CB_ONLY_ED25519: Use only callbacks for Ed25519 default: off
*/

#include <wolfssl/wolfcrypt/libwolfssl_sources.h>
Expand Down Expand Up @@ -156,6 +157,8 @@ static const char* GetPkTypeStr(int pk)
case WC_PK_TYPE_EC_GET_SIG_SIZE: return "ECC GetSigSize";
case WC_PK_TYPE_EC_MAKE_PUB: return "ECC MakePub";
case WC_PK_TYPE_EC_CHECK_PUB_KEY: return "ECC CheckPubKey";
case WC_PK_TYPE_ED25519_MAKE_PUB: return "ED25519 MakePub";
case WC_PK_TYPE_ED25519_CHECK_KEY: return "ED25519 CheckKey";
}
return NULL;
}
Expand Down Expand Up @@ -1167,6 +1170,60 @@ int wc_CryptoCb_Ed25519Verify(const byte* sig, word32 sigLen,

return wc_CryptoCb_TranslateErrorCode(ret);
}

int wc_CryptoCb_Ed25519MakePub(ed25519_key* key, byte* pubKey, word32 pubKeySz)
{
int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
CryptoCb* dev;

if (key == NULL || pubKey == NULL || pubKeySz != ED25519_PUB_KEY_SIZE)
return ret;

/* locate registered callback */
dev = wc_CryptoCb_FindDevice(key->devId, WC_ALGO_TYPE_PK);
if (dev && dev->cb) {
wc_CryptoInfo cryptoInfo;
XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo));
cryptoInfo.algo_type = WC_ALGO_TYPE_PK;
cryptoInfo.pk.type = WC_PK_TYPE_ED25519_MAKE_PUB;
cryptoInfo.pk.ed25519makepub.key = key;
cryptoInfo.pk.ed25519makepub.pubOut = pubKey;
cryptoInfo.pk.ed25519makepub.pubOutSz = pubKeySz;

ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx);
}

return wc_CryptoCb_TranslateErrorCode(ret);
}

int wc_CryptoCb_Ed25519CheckKey(ed25519_key* key)
{
int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
CryptoCb* dev;

if (key == NULL)
return ret;

/* locate registered callback */
dev = wc_CryptoCb_FindDevice(key->devId, WC_ALGO_TYPE_PK);
if (dev && dev->cb) {
wc_CryptoInfo cryptoInfo;
XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo));
cryptoInfo.algo_type = WC_ALGO_TYPE_PK;
cryptoInfo.pk.type = WC_PK_TYPE_ED25519_CHECK_KEY;
cryptoInfo.pk.ed25519checkkey.key = key;
/* key->p is already the compressed wire form; cross it as bytes so a
* device can validate the actual input without touching key internals
*/
cryptoInfo.pk.ed25519checkkey.pubKey = key->p;
cryptoInfo.pk.ed25519checkkey.pubKeySz = ED25519_PUB_KEY_SIZE;
cryptoInfo.pk.ed25519checkkey.checkPriv = key->privKeySet ? 1 : 0;

ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx);
}

return wc_CryptoCb_TranslateErrorCode(ret);
}
#endif /* HAVE_ED25519 */

#if defined(WOLFSSL_HAVE_LMS) || defined(WOLFSSL_HAVE_XMSS)
Expand Down
Loading
Loading