From 0478f9612c95d5636c382d1a4e465963929b4155 Mon Sep 17 00:00:00 2001 From: night1rider Date: Tue, 11 Aug 2026 14:08:08 -0600 Subject: [PATCH] Xilinx Versal Gen2 ASU port: ECC, ECDH and ECIES offload Offload ECDSA/Ed25519/Ed448 sign/verify, ECDH, and ECIES (AES-GCM) to the ASU via the wolfSSL crypto callback; add wc_ecc_ctx_get_mac_salt and the ECC benchmark. --- wolfcrypt/benchmark/benchmark.c | 34 +- wolfcrypt/benchmark/benchmark.h | 2 +- wolfcrypt/src/ecc.c | 11 + .../xilinx/versal_gen2_asu/asu_cryptocb.c | 24 +- .../src/port/xilinx/versal_gen2_asu/asu_ecc.c | 854 ++++++++++++++++++ .../port/xilinx/versal_gen2_asu/asu_ecdh.c | 244 +++++ .../port/xilinx/versal_gen2_asu/asu_ecies.c | 513 +++++++++++ wolfssl/wolfcrypt/ecc.h | 2 + .../port/xilinx/versal_gen2_asu/asu_ecc.h | 54 ++ .../port/xilinx/versal_gen2_asu/asu_ecdh.h | 52 ++ .../port/xilinx/versal_gen2_asu/asu_ecies.h | 50 + 11 files changed, 1830 insertions(+), 10 deletions(-) create mode 100644 wolfcrypt/src/port/xilinx/versal_gen2_asu/asu_ecc.c create mode 100644 wolfcrypt/src/port/xilinx/versal_gen2_asu/asu_ecdh.c create mode 100644 wolfcrypt/src/port/xilinx/versal_gen2_asu/asu_ecies.c create mode 100644 wolfssl/wolfcrypt/port/xilinx/versal_gen2_asu/asu_ecc.h create mode 100644 wolfssl/wolfcrypt/port/xilinx/versal_gen2_asu/asu_ecdh.h create mode 100644 wolfssl/wolfcrypt/port/xilinx/versal_gen2_asu/asu_ecies.h diff --git a/wolfcrypt/benchmark/benchmark.c b/wolfcrypt/benchmark/benchmark.c index b9c867cb213..1362227f9dd 100644 --- a/wolfcrypt/benchmark/benchmark.c +++ b/wolfcrypt/benchmark/benchmark.c @@ -4707,7 +4707,13 @@ static void* benchmarks_do(void* args) (bench_asym_algs & BENCH_ECC_ALL) || (bench_asym_algs & BENCH_ECC_ENCRYPT)) { + /* WOLFSSL_BENCH_ECC_ALL: let a plain 'bench all' run sweep every + * compiled-in curve, not just P-256, so HW/SW compare across all. */ +#ifdef WOLFSSL_BENCH_ECC_ALL + if ((bench_asym_algs & BENCH_ECC_ALL) || bench_all) { +#else if (bench_asym_algs & BENCH_ECC_ALL) { +#endif #if defined(HAVE_FIPS) || defined(HAVE_SELFTEST) printf("%snot supported in FIPS mode (no ending enum value)\n", err_prefix); @@ -13665,8 +13671,14 @@ void bench_ecc_curve(int curveId) #endif } #ifdef HAVE_ECC_ENCRYPT - if (bench_all || (bench_asym_algs & BENCH_ECC_ENCRYPT)) - bench_eccEncrypt(curveId); + if (bench_all || (bench_asym_algs & BENCH_ECC_ENCRYPT)) { + #ifndef NO_SW_BENCH + bench_eccEncrypt(0, curveId); + #endif + #if defined(BENCH_DEVID) + bench_eccEncrypt(1, curveId); + #endif + } #endif } @@ -13834,6 +13846,12 @@ void bench_ecc(int useDeviceID, int curveId) if (dgstSize > WC_MAX_DIGEST_SIZE) { dgstSize = WC_MAX_DIGEST_SIZE; } + /* Small curves (P-192) map to a 20-byte digest, below the ECDSA sign + * minimum, so wc_ecc_sign_hash returns BAD_LENGTH_E. Clamp up so every + * enabled curve can be benchmarked. */ + if (dgstSize < WC_MIN_DIGEST_SIZE_FOR_SIGN) { + dgstSize = WC_MIN_DIGEST_SIZE_FOR_SIGN; + } /* init keys */ for (i = 0; i < BENCH_MAX_PENDING; i++) { @@ -14098,7 +14116,7 @@ static int bench_ecies_prep(ecEncCtx* ctx, byte encAlgo, const byte* ownSalt, return ret; } -void bench_eccEncrypt(int curveId) +void bench_eccEncrypt(int useDeviceID, int curveId) { #define BENCH_ECCENCRYPT_MSG_SIZE 48 #define BENCH_ECCENCRYPT_OUT_SIZE (BENCH_ECCENCRYPT_MSG_SIZE + \ @@ -14140,13 +14158,13 @@ void bench_eccEncrypt(int curveId) #endif keySize = wc_ecc_get_curve_size_from_id(curveId); - ret = wc_ecc_init_ex(userA, HEAP_HINT, devId); + ret = wc_ecc_init_ex(userA, HEAP_HINT, useDeviceID ? devId : INVALID_DEVID); if (ret != 0) { printf("wc_ecc_encrypt make key A failed: %d\n", ret); goto exit; } - ret = wc_ecc_init_ex(userB, HEAP_HINT, devId); + ret = wc_ecc_init_ex(userB, HEAP_HINT, useDeviceID ? devId : INVALID_DEVID); if (ret != 0) { printf("wc_ecc_encrypt make key B failed: %d\n", ret); goto exit; @@ -14212,7 +14230,7 @@ void bench_eccEncrypt(int curveId) #endif #endif #if !defined(NO_AES) && defined(HAVE_AESGCM) && \ - defined(WOLFSSL_ECIES_STATIC_GCM_NONCE) + (defined(WOLFSSL_ECIES_STATIC_GCM_NONCE) || defined(WOLFSSL_ECIES_GEN_IV)) #ifdef WOLFSSL_AES_128 { ecAES_128_GCM, "AES128GCM" }, #endif @@ -14277,7 +14295,7 @@ void bench_eccEncrypt(int curveId) || runs < minimum_runs #endif ); - bench_stats_asym_finish(name, keySize * 8, encDesc, 0, count, start, + bench_stats_asym_finish(name, keySize * 8, encDesc, useDeviceID, count, start, ret); #ifdef MULTI_VALUE_STATISTICS bench_multi_value_stats(max, min, sum, squareSum, runs); @@ -14307,7 +14325,7 @@ void bench_eccEncrypt(int curveId) || runs < minimum_runs #endif ); - bench_stats_asym_finish(name, keySize * 8, decDesc, 0, count, start, + bench_stats_asym_finish(name, keySize * 8, decDesc, useDeviceID, count, start, ret); #ifdef MULTI_VALUE_STATISTICS bench_multi_value_stats(max, min, sum, squareSum, runs); diff --git a/wolfcrypt/benchmark/benchmark.h b/wolfcrypt/benchmark/benchmark.h index 3af7de2d1d5..9d34f854307 100644 --- a/wolfcrypt/benchmark/benchmark.h +++ b/wolfcrypt/benchmark/benchmark.h @@ -116,7 +116,7 @@ void bench_slhdsa(int param); void bench_ecc_curve(int curveId); void bench_eccMakeKey(int useDeviceID, int curveId); void bench_ecc(int useDeviceID, int curveId); -void bench_eccEncrypt(int curveId); +void bench_eccEncrypt(int useDeviceID, int curveId); void bench_sm2(int useDeviceID); void bench_curve25519KeyGen(int useDeviceID); void bench_curve25519KeyAgree(int useDeviceID); diff --git a/wolfcrypt/src/ecc.c b/wolfcrypt/src/ecc.c index 8168d0d21e9..74ca356431f 100644 --- a/wolfcrypt/src/ecc.c +++ b/wolfcrypt/src/ecc.c @@ -14991,6 +14991,17 @@ int wc_ecc_ctx_get_info(ecEncCtx* ctx, const byte** info, word32* sz) return 0; } + +int wc_ecc_ctx_get_mac_salt(ecEncCtx* ctx, const byte** salt, word32* sz) +{ + if (ctx == NULL || salt == NULL || sz == NULL) + return BAD_FUNC_ARG; + + *salt = ctx->macSalt; + *sz = ctx->macSaltSz; + + return 0; +} #endif /* WOLF_CRYPTO_CB */ diff --git a/wolfcrypt/src/port/xilinx/versal_gen2_asu/asu_cryptocb.c b/wolfcrypt/src/port/xilinx/versal_gen2_asu/asu_cryptocb.c index 6cc7d6d8e0e..43e85d4c65f 100644 --- a/wolfcrypt/src/port/xilinx/versal_gen2_asu/asu_cryptocb.c +++ b/wolfcrypt/src/port/xilinx/versal_gen2_asu/asu_cryptocb.c @@ -46,6 +46,15 @@ #ifdef WOLFSSL_VERSAL_GEN2_ASU_CMAC #include #endif +#ifdef WOLFSSL_VERSAL_GEN2_ASU_ECC + #include +#endif +#ifdef WOLFSSL_VERSAL_GEN2_ASU_ECDH + #include +#endif +#ifdef WOLFSSL_VERSAL_GEN2_ASU_ECIES + #include +#endif #ifndef WOLF_CRYPTO_CB #error "WOLFSSL_VERSAL_GEN2_ASU requires WOLF_CRYPTO_CB" @@ -148,7 +157,20 @@ static int wc_AsuCryptoDevCb(int devId, wc_CryptoInfo* info, void* ctx) ret = wc_AsuCmac(info); #endif break; - case WC_ALGO_TYPE_PK: /* M3 asu_rsa and asu_ecc */ + case WC_ALGO_TYPE_PK: /* M3 asu_ecc, asu_ecdh and asu_ecies */ + #ifdef WOLFSSL_VERSAL_GEN2_ASU_ECC + ret = wc_AsuEcc(info); + #endif + #ifdef WOLFSSL_VERSAL_GEN2_ASU_ECDH + if (ret == WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { + ret = wc_AsuEcdh(info); + } + #endif + #ifdef WOLFSSL_VERSAL_GEN2_ASU_ECIES + if (ret == WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) { + ret = wc_AsuEcies(info); + } + #endif break; case WC_ALGO_TYPE_COPY: /* context copy: route by sub-algo to its engine */ ret = wc_AsuCopy(info); diff --git a/wolfcrypt/src/port/xilinx/versal_gen2_asu/asu_ecc.c b/wolfcrypt/src/port/xilinx/versal_gen2_asu/asu_ecc.c new file mode 100644 index 00000000000..e470c03e049 --- /dev/null +++ b/wolfcrypt/src/port/xilinx/versal_gen2_asu/asu_ecc.c @@ -0,0 +1,854 @@ +/* asu_ecc.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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 + */ + +/* ASU ECDSA offload for the wolfSSL crypto callback. + * + * wolfSSL delivers the message digest to the sign/verify callbacks; the ASU does + * the curve operation. The ASU works in raw fixed-width r||s and raw keys, while + * wolfSSL passes a DER signature, so sign converts the ASU's raw r||s to DER with + * wc_ecc_rs_raw_to_sig and verify converts the DER signature to raw with + * wc_ecc_sig_to_rs (right-aligned to the curve width). Keys are written as + * fixed-width byte arrays with mp_to_unsigned_bin_len. Verify returns pass or fail + * in the ASU additional status (fail-closed). NIST P-192/256/384/521 and + * Brainpool P-256/320/384/512 prime curves. + * + * Ed25519 sign/verify also route here: the ASU hashes the raw message internally, + * so wolfSSL's message is passed through the digest parameter, the private seed and + * compressed public key through the key parameter, and the standard 64-byte + * signature through the sign parameter. Only plain Ed25519 (no context, no prehash) + * maps to the ASU; ctx/ph variants decline to software. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#ifdef WOLFSSL_VERSAL_GEN2_ASU_ECC + +#include +#include +#include +#include +#include +#ifdef HAVE_ED25519 +#include +#endif +#ifdef HAVE_ED448 +#include +#endif + +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#endif + +#include "xasu_ecc.h" +#include "xasu_eccinfo.h" +#include "xasu_shainfo.h" +#include "xasu_status.h" +#include "xstatus.h" + +#if defined(NO_ECC) || !defined(HAVE_ECC) + #error "WOLFSSL_VERSAL_GEN2_ASU_ECC requires ECC" +#endif + +/* Submit-thunk op selector. */ +#define WC_ASU_ECC_OP_SIGN 0 /* XAsu_EccGenSign */ +#define WC_ASU_ECC_OP_VERIFY 1 /* XAsu_EccVerifySign */ + +/* Largest supported curve width (NIST P-521, 66 bytes). */ +#define WC_ASU_ECC_MAX_KEYLEN XASU_ECC_P521_SIZE_IN_BYTES + +/* One ASU ECC request. The buffers are heap-resident so the ASU DMA can reach + * them; the caller's digest/signature may live in non-DMA memory. */ +typedef struct { + XAsu_EccParams params; + byte key[2U * WC_ASU_ECC_MAX_KEYLEN]; /* priv d (sign) or Qx||Qy (verify) */ + byte digest[XASU_SHA_512_HASH_LEN]; /* message digest, <= 64 bytes */ + byte sign[2U * WC_ASU_ECC_MAX_KEYLEN]; /* raw r||s */ + int op; +} AsuEccReq; + +/* Submit thunk: queue one ASU ECC operation. */ +static int wc_AsuEccSubmit(XAsu_ClientParams* params, void* ctx) +{ + AsuEccReq* req = (AsuEccReq*)ctx; + + if (params == NULL || req == NULL) { + return XST_FAILURE; + } + switch (req->op) { + case WC_ASU_ECC_OP_SIGN: + return XAsu_EccGenSign(params, &req->params); + case WC_ASU_ECC_OP_VERIFY: + return XAsu_EccVerifySign(params, &req->params); + default: + return XST_FAILURE; + } +} + +/* Map the wolfSSL curve id to an ASU CurveType and byte length, declining any + * curve that is not a supported NIST or Brainpool prime curve so wolfSSL falls + * back to software. The key's domain size must match the mapped length. */ +static int wc_AsuEccCurve(ecc_key* key, u32* curveType, u32* keyLen) +{ + u32 type; + u32 len; + + if (key == NULL || curveType == NULL || keyLen == NULL) { + return BAD_FUNC_ARG; + } + if (key->dp == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + switch (key->dp->id) { + case ECC_SECP192R1: + type = (u32)XASU_ECC_NIST_P192; + len = (u32)XASU_ECC_P192_SIZE_IN_BYTES; + break; + case ECC_SECP256R1: + type = (u32)XASU_ECC_NIST_P256; + len = (u32)XASU_ECC_P256_SIZE_IN_BYTES; + break; + case ECC_SECP384R1: + type = (u32)XASU_ECC_NIST_P384; + len = (u32)XASU_ECC_P384_SIZE_IN_BYTES; + break; +#ifdef WOLFSSL_VERSAL_GEN2_ASU_ECC_P521 + /* Off by default: stock firmware mis-pads the digest and the client caps it + * at 64 < P-521's 66-byte width. Enable only with firmware that front-pads. */ + case ECC_SECP521R1: + type = (u32)XASU_ECC_NIST_P521; + len = (u32)XASU_ECC_P521_SIZE_IN_BYTES; + break; +#endif +#ifdef HAVE_ECC_BRAINPOOL + /* Brainpool prime curves: standard ECDSA, same digest handling as the NIST + * curves (every width <= the 64-byte ASU digest cap, so no P-521 issue). */ + case ECC_BRAINPOOLP256R1: + type = (u32)XASU_ECC_BRAINPOOL_P256; + len = (u32)XASU_ECC_P256_SIZE_IN_BYTES; + break; + case ECC_BRAINPOOLP320R1: + type = (u32)XASU_ECC_BRAINPOOL_P320; + len = (u32)XASU_ECC_P320_SIZE_IN_BYTES; + break; + case ECC_BRAINPOOLP384R1: + type = (u32)XASU_ECC_BRAINPOOL_P384; + len = (u32)XASU_ECC_P384_SIZE_IN_BYTES; + break; + case ECC_BRAINPOOLP512R1: + type = (u32)XASU_ECC_BRAINPOOL_P512; + len = (u32)XASU_ECC_P512_SIZE_IN_BYTES; + break; +#endif + default: + return CRYPTOCB_UNAVAILABLE; + } + if ((u32)key->dp->size != len) { + return CRYPTOCB_UNAVAILABLE; + } + *curveType = type; + *keyLen = len; + return 0; +} + +/* Right-align a short digest to the curve width (capped at 64) so the ASU reads the + * same integer as software; a digest at least the width keeps its leftmost bytes. */ +static void wc_AsuEccDigest(const byte* hash, word32 hashLen, byte* out, u32 width) +{ + XMEMSET(out, 0, width); + if (hashLen >= width) { + XMEMCPY(out, hash, width); + } + else { + XMEMCPY(out + (width - hashLen), hash, hashLen); + } +} + +/* True if the digest is entirely zero. An all-zero digest (z = 0) is a degenerate + * ECDSA input the ASU does not sign/verify correctly, so it is deferred to + * software (wolfcrypt_test's ecc_test_curve_size exercises this case). */ +static int wc_AsuEccZeroDigest(const byte* d, word32 len) +{ + word32 i; + + for (i = 0; i < len; i++) { + if (d[i] != 0) { + return 0; + } + } + return 1; +} + +/* Full-hardware ECDSA sign. wolfSSL passes the digest in info->pk.eccsign.in; the + * ASU signs it with the private key and returns raw r||s, which is encoded to a + * DER signature in info->pk.eccsign.out. */ +static int wc_AsuEccSign(wc_CryptoInfo* info) +{ + WC_DECLARE_VAR(req, AsuEccReq, 1, NULL); + ecc_key* key = info->pk.eccsign.key; + u32 curveType = 0; + u32 keyLen = 0; + u32 digLen; + word32 status; + word32 addl = 0; + int ret = 0; + + if (key == NULL || info->pk.eccsign.in == NULL || + info->pk.eccsign.out == NULL || info->pk.eccsign.outlen == NULL) { + return BAD_FUNC_ARG; + } + if (info->pk.eccsign.inlen == 0) { + return CRYPTOCB_UNAVAILABLE; + } + if (wc_AsuEccZeroDigest(info->pk.eccsign.in, info->pk.eccsign.inlen)) { + return CRYPTOCB_UNAVAILABLE; + } + + ret = wc_AsuEccCurve(key, &curveType, &keyLen); + if (ret != 0) { + return ret; + } + /* Private key is required to sign. */ + if (mp_iszero(key->k) || mp_unsigned_bin_size(key->k) > (int)keyLen) { + return CRYPTOCB_UNAVAILABLE; + } + /* Digest width fed to the ASU: the curve length, capped at the ASU maximum. */ + digLen = keyLen; + if (digLen > (u32)XASU_SHA_512_HASH_LEN) { + digLen = (u32)XASU_SHA_512_HASH_LEN; + } + + WC_ALLOC_VAR_EX(req, AsuEccReq, 1, NULL, DYNAMIC_TYPE_TMP_BUFFER, + ret = MEMORY_E); + if (!WC_VAR_OK(req)) { + return ret; + } + + XMEMSET(req, 0, sizeof(*req)); + if (mp_to_unsigned_bin_len(key->k, req->key, (int)keyLen) < 0) { + ret = WC_HW_E; + goto out; + } + wc_AsuEccDigest(info->pk.eccsign.in, info->pk.eccsign.inlen, req->digest, + digLen); + + req->op = WC_ASU_ECC_OP_SIGN; + req->params.CurveType = curveType; + req->params.KeyLen = keyLen; + req->params.DigestLen = digLen; + req->params.KeyAddr = (u64)(UINTPTR)req->key; + req->params.DigestAddr = (u64)(UINTPTR)req->digest; + req->params.SignAddr = (u64)(UINTPTR)req->sign; + + WC_ASU_PRINTF("[ASU] ecc sign curve=%u keyLen=%u digestLen=%u\r\n", + (unsigned int)curveType, (unsigned int)keyLen, (unsigned int)digLen); + + wc_AsuCacheFlush(req->key, keyLen); + wc_AsuCacheFlush(req->digest, digLen); + wc_AsuCacheFlush(req->sign, 2U * keyLen); + + status = wc_AsuTransact(wc_AsuEccSubmit, req, &addl); + + wc_AsuCacheInvalidate(req->sign, 2U * keyLen); + + WC_ASU_PRINTF("[ASU] ecc sign st=%u addl=0x%x\r\n", + (unsigned int)status, (unsigned int)addl); + + if (status != XST_SUCCESS) { + /* The sign inputs were validated before the op, so a failure here is a + * real hardware fault, not an unsupported request: surface it. */ + ret = WC_HW_E; + goto out; + } + /* Encode the ASU's raw r||s (each keyLen bytes) as a DER signature. */ + ret = wc_ecc_rs_raw_to_sig(req->sign, keyLen, req->sign + keyLen, keyLen, + info->pk.eccsign.out, info->pk.eccsign.outlen); + +out: + /* Scrub the private key copied into req->key before freeing. */ + ForceZero(req, sizeof(*req)); + WC_FREE_VAR_EX(req, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return ret; +} + +/* Full-hardware ECDSA verify. The DER signature is converted to raw r||s and the + * ASU checks it; *res is set to 1 only on a verified result (fail-closed). */ +static int wc_AsuEccVerify(wc_CryptoInfo* info) +{ + WC_DECLARE_VAR(req, AsuEccReq, 1, NULL); + ecc_key* key = info->pk.eccverify.key; + u32 curveType = 0; + u32 keyLen = 0; + u32 digLen; + word32 rLen; + word32 sLen; + word32 status; + word32 addl = 0; + int ret = 0; + + if (info->pk.eccverify.res == NULL) { + return BAD_FUNC_ARG; + } + *info->pk.eccverify.res = 0; + + if (key == NULL || info->pk.eccverify.sig == NULL || + info->pk.eccverify.hash == NULL) { + return BAD_FUNC_ARG; + } + if (info->pk.eccverify.hashlen == 0) { + return CRYPTOCB_UNAVAILABLE; + } + if (wc_AsuEccZeroDigest(info->pk.eccverify.hash, info->pk.eccverify.hashlen)) { + return CRYPTOCB_UNAVAILABLE; + } + + ret = wc_AsuEccCurve(key, &curveType, &keyLen); + if (ret != 0) { + return ret; + } + /* Digest width fed to the ASU: the curve length, capped at the ASU maximum. */ + digLen = keyLen; + if (digLen > (u32)XASU_SHA_512_HASH_LEN) { + digLen = (u32)XASU_SHA_512_HASH_LEN; + } + + WC_ALLOC_VAR_EX(req, AsuEccReq, 1, NULL, DYNAMIC_TYPE_TMP_BUFFER, + ret = MEMORY_E); + if (!WC_VAR_OK(req)) { + return ret; + } + + XMEMSET(req, 0, sizeof(*req)); + /* The ASU verify needs the public key. A private-only key (public point not + * populated) marshals to zeros, which the ASU cannot verify, so let software + * handle it rather than sending an invalid key. */ + if (mp_iszero(key->pubkey.x) && mp_iszero(key->pubkey.y)) { + ret = CRYPTOCB_UNAVAILABLE; + goto out; + } + /* Public key Qx||Qy, each left-zero-padded to the curve width. */ + if (mp_to_unsigned_bin_len(key->pubkey.x, req->key, (int)keyLen) < 0 || + mp_to_unsigned_bin_len(key->pubkey.y, req->key + keyLen, + (int)keyLen) < 0) { + ret = WC_HW_E; + goto out; + } + + /* DER signature -> raw r,s (natural length), right-aligned to keyLen each. */ + rLen = keyLen; + sLen = keyLen; + ret = wc_ecc_sig_to_rs(info->pk.eccverify.sig, info->pk.eccverify.siglen, + req->sign, &rLen, req->sign + keyLen, &sLen); + if (ret != 0 || rLen > keyLen || sLen > keyLen) { + ret = CRYPTOCB_UNAVAILABLE; + goto out; + } + if (rLen < keyLen) { + XMEMMOVE(req->sign + (keyLen - rLen), req->sign, rLen); + XMEMSET(req->sign, 0, keyLen - rLen); + } + if (sLen < keyLen) { + XMEMMOVE(req->sign + keyLen + (keyLen - sLen), req->sign + keyLen, sLen); + XMEMSET(req->sign + keyLen, 0, keyLen - sLen); + } + + wc_AsuEccDigest(info->pk.eccverify.hash, info->pk.eccverify.hashlen, + req->digest, digLen); + + req->op = WC_ASU_ECC_OP_VERIFY; + req->params.CurveType = curveType; + req->params.KeyLen = keyLen; + req->params.DigestLen = digLen; + req->params.KeyAddr = (u64)(UINTPTR)req->key; + req->params.DigestAddr = (u64)(UINTPTR)req->digest; + req->params.SignAddr = (u64)(UINTPTR)req->sign; + + WC_ASU_PRINTF("[ASU] ecc verify curve=%u keyLen=%u digestLen=%u\r\n", + (unsigned int)curveType, (unsigned int)keyLen, (unsigned int)digLen); + + wc_AsuCacheFlush(req->key, 2U * keyLen); + wc_AsuCacheFlush(req->digest, digLen); + wc_AsuCacheFlush(req->sign, 2U * keyLen); + + status = wc_AsuTransact(wc_AsuEccSubmit, req, &addl); + + WC_ASU_PRINTF("[ASU] ecc verify st=%u addl=0x%x\r\n", + (unsigned int)status, (unsigned int)addl); + + /* Trust the ASU verdict when it completed (status success): VERIFIED sets + * res=1, otherwise res stays 0. Inputs the ASU cannot verify are declined + * before the op, so a status failure here is a real hardware fault. */ + if (status != XST_SUCCESS) { + ret = WC_HW_E; + } + else { + if (addl == (word32)XASU_ECC_SIGNATURE_VERIFIED) { + *info->pk.eccverify.res = 1; + } + ret = 0; + } + +out: + WC_FREE_VAR_EX(req, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return ret; +} + +#ifdef HAVE_ED25519 + +/* Full-hardware Ed25519 sign. The ASU hashes the message itself: message in the + * digest field, 32-byte seed in the key field; plain Ed25519 only. */ +static int wc_AsuEd25519Sign(wc_CryptoInfo* info) +{ + WC_DECLARE_VAR(req, AsuEccReq, 1, NULL); + ed25519_key* key = info->pk.ed25519sign.key; + byte* msg = NULL; + word32 msgLen; + word32 status; + word32 addl = 0; + int ret = 0; + + if (key == NULL || info->pk.ed25519sign.out == NULL || + info->pk.ed25519sign.outLen == NULL) { + return BAD_FUNC_ARG; + } + /* The ASU implements only plain Ed25519; ctx/prehash or a context string defer + * to software. Ed25519 is -1, stored in the byte type field. */ + if (info->pk.ed25519sign.type != (byte)Ed25519 || + info->pk.ed25519sign.contextLen != 0) { + return CRYPTOCB_UNAVAILABLE; + } + /* Like software, signing needs both the seed and the public key set; software + * rejects a private-only key, so decline instead of signing on hardware. */ + if (key->privKeySet == 0 || key->pubKeySet == 0) { + return CRYPTOCB_UNAVAILABLE; + } + if (*info->pk.ed25519sign.outLen < ED25519_SIG_SIZE) { + return CRYPTOCB_UNAVAILABLE; + } + msgLen = info->pk.ed25519sign.inLen; + if (msgLen != 0 && info->pk.ed25519sign.in == NULL) { + return BAD_FUNC_ARG; + } + + WC_ALLOC_VAR_EX(req, AsuEccReq, 1, NULL, DYNAMIC_TYPE_TMP_BUFFER, + ret = MEMORY_E); + if (!WC_VAR_OK(req)) { + return ret; + } + XMEMSET(req, 0, sizeof(*req)); + + /* DMA-resident copy of the message; the (zeroed) digest field is the non-NULL + * stand-in the client requires for a zero-length message. */ + if (msgLen != 0) { + msg = (byte*)XMALLOC(msgLen, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (msg == NULL) { + ret = MEMORY_E; + goto out; + } + XMEMCPY(msg, info->pk.ed25519sign.in, msgLen); + } + else { + msg = req->digest; + } + + XMEMCPY(req->key, key->k, ED25519_KEY_SIZE); + + req->op = WC_ASU_ECC_OP_SIGN; + req->params.CurveType = (u32)XASU_ECC_NIST_ED25519; + req->params.KeyLen = (u32)ED25519_KEY_SIZE; + req->params.DigestLen = msgLen; + req->params.KeyAddr = (u64)(UINTPTR)req->key; + req->params.DigestAddr = (u64)(UINTPTR)msg; + req->params.SignAddr = (u64)(UINTPTR)req->sign; + + WC_ASU_PRINTF("[ASU] ed25519 sign msgLen=%u\r\n", (unsigned int)msgLen); + + wc_AsuCacheFlush(req->key, ED25519_KEY_SIZE); + wc_AsuCacheFlush(req->sign, ED25519_SIG_SIZE); + if (msgLen != 0) { + wc_AsuCacheFlush(msg, msgLen); + } + + status = wc_AsuTransact(wc_AsuEccSubmit, req, &addl); + + wc_AsuCacheInvalidate(req->sign, ED25519_SIG_SIZE); + + WC_ASU_PRINTF("[ASU] ed25519 sign st=%u\r\n", (unsigned int)status); + + if (status != XST_SUCCESS) { + /* Inputs were validated; a failure here is a real hardware fault. */ + ret = WC_HW_E; + goto out; + } + XMEMCPY(info->pk.ed25519sign.out, req->sign, ED25519_SIG_SIZE); + *info->pk.ed25519sign.outLen = ED25519_SIG_SIZE; + ret = 0; + +out: + if (msgLen != 0 && msg != NULL) { + XFREE(msg, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + /* Scrub the private seed copied into req->key before freeing. */ + ForceZero(req, sizeof(*req)); + WC_FREE_VAR_EX(req, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return ret; +} + +/* Full-hardware Ed25519 verify. The key buffer is 32 zero bytes then the compressed + * public key; *res is set to 1 only on a verified result (fail-closed). */ +static int wc_AsuEd25519Verify(wc_CryptoInfo* info) +{ + WC_DECLARE_VAR(req, AsuEccReq, 1, NULL); + ed25519_key* key = info->pk.ed25519verify.key; + byte* msg = NULL; + word32 msgLen; + word32 status; + word32 addl = 0; + int ret = 0; + + if (info->pk.ed25519verify.res == NULL) { + return BAD_FUNC_ARG; + } + *info->pk.ed25519verify.res = 0; + + if (key == NULL || info->pk.ed25519verify.sig == NULL) { + return BAD_FUNC_ARG; + } + if (info->pk.ed25519verify.type != (byte)Ed25519 || + info->pk.ed25519verify.contextLen != 0) { + return CRYPTOCB_UNAVAILABLE; + } + if (key->pubKeySet == 0) { + return CRYPTOCB_UNAVAILABLE; + } + if (info->pk.ed25519verify.sigLen != ED25519_SIG_SIZE) { + return CRYPTOCB_UNAVAILABLE; + } + msgLen = info->pk.ed25519verify.msgLen; + if (msgLen != 0 && info->pk.ed25519verify.msg == NULL) { + return BAD_FUNC_ARG; + } + + WC_ALLOC_VAR_EX(req, AsuEccReq, 1, NULL, DYNAMIC_TYPE_TMP_BUFFER, + ret = MEMORY_E); + if (!WC_VAR_OK(req)) { + return ret; + } + XMEMSET(req, 0, sizeof(*req)); + + if (msgLen != 0) { + msg = (byte*)XMALLOC(msgLen, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (msg == NULL) { + ret = MEMORY_E; + goto out; + } + XMEMCPY(msg, info->pk.ed25519verify.msg, msgLen); + } + else { + msg = req->digest; + } + + /* Public-key buffer: leading zero Qx then the compressed key in the Qy half. */ + XMEMCPY(req->key + ED25519_PUB_KEY_SIZE, key->p, ED25519_PUB_KEY_SIZE); + XMEMCPY(req->sign, info->pk.ed25519verify.sig, ED25519_SIG_SIZE); + + req->op = WC_ASU_ECC_OP_VERIFY; + req->params.CurveType = (u32)XASU_ECC_NIST_ED25519; + req->params.KeyLen = (u32)ED25519_KEY_SIZE; + req->params.DigestLen = msgLen; + req->params.KeyAddr = (u64)(UINTPTR)req->key; + req->params.DigestAddr = (u64)(UINTPTR)msg; + req->params.SignAddr = (u64)(UINTPTR)req->sign; + + WC_ASU_PRINTF("[ASU] ed25519 verify msgLen=%u\r\n", (unsigned int)msgLen); + + wc_AsuCacheFlush(req->key, 2U * ED25519_KEY_SIZE); + wc_AsuCacheFlush(req->sign, ED25519_SIG_SIZE); + if (msgLen != 0) { + wc_AsuCacheFlush(msg, msgLen); + } + + status = wc_AsuTransact(wc_AsuEccSubmit, req, &addl); + + WC_ASU_PRINTF("[ASU] ed25519 verify st=%u addl=0x%x\r\n", + (unsigned int)status, (unsigned int)addl); + + /* Trust the ASU verdict when the op ran: VERIFIED sets res=1, otherwise res + * stays 0. A status failure is a real hardware fault (see wc_AsuEccVerify). */ + if (status != XST_SUCCESS) { + ret = WC_HW_E; + } + else { + if (addl == (word32)XASU_ECC_SIGNATURE_VERIFIED) { + *info->pk.ed25519verify.res = 1; + } + ret = 0; + } + +out: + if (msgLen != 0 && msg != NULL) { + XFREE(msg, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + WC_FREE_VAR_EX(req, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return ret; +} + +#endif /* HAVE_ED25519 */ + +#ifdef HAVE_ED448 + +/* Full-hardware Ed448 sign. Like Ed25519: message in the digest field, 57-byte + * seed in the key field; plain Ed448 only (context/prehash decline to software). */ +static int wc_AsuEd448Sign(wc_CryptoInfo* info) +{ + WC_DECLARE_VAR(req, AsuEccReq, 1, NULL); + ed448_key* key = info->pk.ed448sign.key; + byte* msg = NULL; + word32 msgLen; + word32 status; + word32 addl = 0; + int ret = 0; + + if (key == NULL || info->pk.ed448sign.out == NULL || + info->pk.ed448sign.outLen == NULL) { + return BAD_FUNC_ARG; + } + /* The ASU implements only plain Ed448; ctx/prehash or a context string defer to + * software. Ed448 is 0, stored in the byte type field. */ + if (info->pk.ed448sign.type != (byte)Ed448 || + info->pk.ed448sign.contextLen != 0) { + return CRYPTOCB_UNAVAILABLE; + } + /* Match software's precondition: signing needs both the private seed and the + * public key set. wolfSSL rejects a private-only key with BAD_FUNC_ARG, so + * decline rather than sign it on hardware. */ + if (key->privKeySet == 0 || key->pubKeySet == 0) { + return CRYPTOCB_UNAVAILABLE; + } + if (*info->pk.ed448sign.outLen < ED448_SIG_SIZE) { + return CRYPTOCB_UNAVAILABLE; + } + msgLen = info->pk.ed448sign.inLen; + if (msgLen != 0 && info->pk.ed448sign.in == NULL) { + return BAD_FUNC_ARG; + } + + WC_ALLOC_VAR_EX(req, AsuEccReq, 1, NULL, DYNAMIC_TYPE_TMP_BUFFER, + ret = MEMORY_E); + if (!WC_VAR_OK(req)) { + return ret; + } + XMEMSET(req, 0, sizeof(*req)); + + if (msgLen != 0) { + msg = (byte*)XMALLOC(msgLen, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (msg == NULL) { + ret = MEMORY_E; + goto out; + } + XMEMCPY(msg, info->pk.ed448sign.in, msgLen); + } + else { + msg = req->digest; + } + + XMEMCPY(req->key, key->k, ED448_KEY_SIZE); + + req->op = WC_ASU_ECC_OP_SIGN; + req->params.CurveType = (u32)XASU_ECC_NIST_ED448; + req->params.KeyLen = (u32)ED448_KEY_SIZE; + req->params.DigestLen = msgLen; + req->params.KeyAddr = (u64)(UINTPTR)req->key; + req->params.DigestAddr = (u64)(UINTPTR)msg; + req->params.SignAddr = (u64)(UINTPTR)req->sign; + + WC_ASU_PRINTF("[ASU] ed448 sign msgLen=%u\r\n", (unsigned int)msgLen); + + wc_AsuCacheFlush(req->key, ED448_KEY_SIZE); + wc_AsuCacheFlush(req->sign, ED448_SIG_SIZE); + if (msgLen != 0) { + wc_AsuCacheFlush(msg, msgLen); + } + + status = wc_AsuTransact(wc_AsuEccSubmit, req, &addl); + + wc_AsuCacheInvalidate(req->sign, ED448_SIG_SIZE); + + WC_ASU_PRINTF("[ASU] ed448 sign st=%u\r\n", (unsigned int)status); + + if (status != XST_SUCCESS) { + /* Inputs were validated; a failure here is a real hardware fault. */ + ret = WC_HW_E; + goto out; + } + XMEMCPY(info->pk.ed448sign.out, req->sign, ED448_SIG_SIZE); + *info->pk.ed448sign.outLen = ED448_SIG_SIZE; + ret = 0; + +out: + if (msgLen != 0 && msg != NULL) { + XFREE(msg, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + /* Scrub the private seed copied into req->key before freeing. */ + ForceZero(req, sizeof(*req)); + WC_FREE_VAR_EX(req, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return ret; +} + +/* Full-hardware Ed448 verify. The key buffer is 57 zero bytes then the compressed + * public key; *res is set to 1 only on a verified result (fail-closed). */ +static int wc_AsuEd448Verify(wc_CryptoInfo* info) +{ + WC_DECLARE_VAR(req, AsuEccReq, 1, NULL); + ed448_key* key = info->pk.ed448verify.key; + byte* msg = NULL; + word32 msgLen; + word32 status; + word32 addl = 0; + int ret = 0; + + if (info->pk.ed448verify.res == NULL) { + return BAD_FUNC_ARG; + } + *info->pk.ed448verify.res = 0; + + if (key == NULL || info->pk.ed448verify.sig == NULL) { + return BAD_FUNC_ARG; + } + if (info->pk.ed448verify.type != (byte)Ed448 || + info->pk.ed448verify.contextLen != 0) { + return CRYPTOCB_UNAVAILABLE; + } + if (key->pubKeySet == 0) { + return CRYPTOCB_UNAVAILABLE; + } + if (info->pk.ed448verify.sigLen != ED448_SIG_SIZE) { + return CRYPTOCB_UNAVAILABLE; + } + msgLen = info->pk.ed448verify.msgLen; + if (msgLen != 0 && info->pk.ed448verify.msg == NULL) { + return BAD_FUNC_ARG; + } + + WC_ALLOC_VAR_EX(req, AsuEccReq, 1, NULL, DYNAMIC_TYPE_TMP_BUFFER, + ret = MEMORY_E); + if (!WC_VAR_OK(req)) { + return ret; + } + XMEMSET(req, 0, sizeof(*req)); + + if (msgLen != 0) { + msg = (byte*)XMALLOC(msgLen, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (msg == NULL) { + ret = MEMORY_E; + goto out; + } + XMEMCPY(msg, info->pk.ed448verify.msg, msgLen); + } + else { + msg = req->digest; + } + + /* Public-key buffer: leading zero Qx then the compressed key in the Qy half. */ + XMEMCPY(req->key + ED448_PUB_KEY_SIZE, key->p, ED448_PUB_KEY_SIZE); + XMEMCPY(req->sign, info->pk.ed448verify.sig, ED448_SIG_SIZE); + + req->op = WC_ASU_ECC_OP_VERIFY; + req->params.CurveType = (u32)XASU_ECC_NIST_ED448; + req->params.KeyLen = (u32)ED448_KEY_SIZE; + req->params.DigestLen = msgLen; + req->params.KeyAddr = (u64)(UINTPTR)req->key; + req->params.DigestAddr = (u64)(UINTPTR)msg; + req->params.SignAddr = (u64)(UINTPTR)req->sign; + + WC_ASU_PRINTF("[ASU] ed448 verify msgLen=%u\r\n", (unsigned int)msgLen); + + wc_AsuCacheFlush(req->key, 2U * ED448_KEY_SIZE); + wc_AsuCacheFlush(req->sign, ED448_SIG_SIZE); + if (msgLen != 0) { + wc_AsuCacheFlush(msg, msgLen); + } + + status = wc_AsuTransact(wc_AsuEccSubmit, req, &addl); + + WC_ASU_PRINTF("[ASU] ed448 verify st=%u addl=0x%x\r\n", + (unsigned int)status, (unsigned int)addl); + + if (status != XST_SUCCESS) { + ret = WC_HW_E; + } + else { + if (addl == (word32)XASU_ECC_SIGNATURE_VERIFIED) { + *info->pk.ed448verify.res = 1; + } + ret = 0; + } + +out: + if (msgLen != 0 && msg != NULL) { + XFREE(msg, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + WC_FREE_VAR_EX(req, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return ret; +} + +#endif /* HAVE_ED448 */ + +/* WC_ALGO_TYPE_PK entry point for ECC: dispatch ECDSA sign and verify. Other ECC + * operations and unsupported curves decline to software. */ +int wc_AsuEcc(wc_CryptoInfo* info) +{ + if (info == NULL) { + return BAD_FUNC_ARG; + } + if (info->algo_type != WC_ALGO_TYPE_PK) { + return CRYPTOCB_UNAVAILABLE; + } + + switch (info->pk.type) { + case WC_PK_TYPE_ECDSA_SIGN: + return wc_AsuEccSign(info); + case WC_PK_TYPE_ECDSA_VERIFY: + return wc_AsuEccVerify(info); +#ifdef HAVE_ED25519 + case WC_PK_TYPE_ED25519_SIGN: + return wc_AsuEd25519Sign(info); + case WC_PK_TYPE_ED25519_VERIFY: + return wc_AsuEd25519Verify(info); +#endif +#ifdef HAVE_ED448 + case WC_PK_TYPE_ED448: + return wc_AsuEd448Sign(info); + case WC_PK_TYPE_ED448_VERIFY: + return wc_AsuEd448Verify(info); +#endif + default: + return CRYPTOCB_UNAVAILABLE; + } +} + +#endif /* WOLFSSL_VERSAL_GEN2_ASU_ECC */ diff --git a/wolfcrypt/src/port/xilinx/versal_gen2_asu/asu_ecdh.c b/wolfcrypt/src/port/xilinx/versal_gen2_asu/asu_ecdh.c new file mode 100644 index 00000000000..10ea675a929 --- /dev/null +++ b/wolfcrypt/src/port/xilinx/versal_gen2_asu/asu_ecdh.c @@ -0,0 +1,244 @@ +/* asu_ecdh.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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 + */ + +/* ASU ECDH offload for the wolfSSL crypto callback. + * + * wolfSSL's wc_ecc_shared_secret callback delivers our private key and the peer's + * public key; the ASU computes the X9.63 shared secret, the X coordinate of + * private_key->k * public_key->pubkey. The private scalar and the peer point Qx||Qy + * are marshalled big-endian, fixed-width with mp_to_unsigned_bin_len. The ASU + * returns the curve-width X coordinate, which matches wolfSSL's fixed-width software + * output (zero-padded to the curve size). NIST P-192/256/384/521 and Brainpool + * P-256/320/384/512 prime curves; other curves decline to software. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#ifdef WOLFSSL_VERSAL_GEN2_ASU_ECDH + +#include +#include +#include +#include +#include + +#include "xasu_ecc.h" +#include "xasu_eccinfo.h" +#include "xasu_status.h" +#include "xstatus.h" + +#if defined(NO_ECC) || !defined(HAVE_ECC) + #error "WOLFSSL_VERSAL_GEN2_ASU_ECDH requires ECC" +#endif +#ifndef HAVE_ECC_DHE + #error "WOLFSSL_VERSAL_GEN2_ASU_ECDH requires HAVE_ECC_DHE" +#endif + +/* Largest supported curve width (NIST P-521, 66 bytes). */ +#define WC_ASU_ECDH_MAX_KEYLEN XASU_ECC_P521_SIZE_IN_BYTES + +/* One ASU ECDH request: our private scalar, the peer public point, and the + * resulting shared secret, all heap-resident so the ASU DMA can reach them. */ +typedef struct { + XAsu_EcdhParams params; + byte privKey[WC_ASU_ECDH_MAX_KEYLEN]; /* our private d */ + byte pubKey[2U * WC_ASU_ECDH_MAX_KEYLEN]; /* peer Qx||Qy */ + byte secret[WC_ASU_ECDH_MAX_KEYLEN]; /* shared secret (X coordinate) */ +} AsuEcdhReq; + +/* Submit thunk: queue one ASU ECDH shared-secret operation. */ +static int wc_AsuEcdhSubmit(XAsu_ClientParams* params, void* ctx) +{ + AsuEcdhReq* req = (AsuEcdhReq*)ctx; + + if (params == NULL || req == NULL) { + return XST_FAILURE; + } + return XAsu_EcdhGenSharedSecret(params, &req->params); +} + +/* Map the wolfSSL curve id to an ASU CurveType and byte length for the supported + * NIST and Brainpool prime curves, declining any other curve so wolfSSL falls back + * to software. The key's domain size must match the mapped length. */ +static int wc_AsuEcdhCurve(ecc_key* key, u32* curveType, u32* keyLen) +{ + u32 type; + u32 len; + + if (key == NULL || curveType == NULL || keyLen == NULL) { + return BAD_FUNC_ARG; + } + if (key->dp == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + switch (key->dp->id) { + case ECC_SECP192R1: + type = (u32)XASU_ECC_NIST_P192; + len = (u32)XASU_ECC_P192_SIZE_IN_BYTES; + break; + case ECC_SECP256R1: + type = (u32)XASU_ECC_NIST_P256; + len = (u32)XASU_ECC_P256_SIZE_IN_BYTES; + break; + case ECC_SECP384R1: + type = (u32)XASU_ECC_NIST_P384; + len = (u32)XASU_ECC_P384_SIZE_IN_BYTES; + break; +#ifdef WOLFSSL_VERSAL_GEN2_ASU_ECC_P521 + case ECC_SECP521R1: + type = (u32)XASU_ECC_NIST_P521; + len = (u32)XASU_ECC_P521_SIZE_IN_BYTES; + break; +#endif +#ifdef HAVE_ECC_BRAINPOOL + case ECC_BRAINPOOLP256R1: + type = (u32)XASU_ECC_BRAINPOOL_P256; + len = (u32)XASU_ECC_P256_SIZE_IN_BYTES; + break; + case ECC_BRAINPOOLP320R1: + type = (u32)XASU_ECC_BRAINPOOL_P320; + len = (u32)XASU_ECC_P320_SIZE_IN_BYTES; + break; + case ECC_BRAINPOOLP384R1: + type = (u32)XASU_ECC_BRAINPOOL_P384; + len = (u32)XASU_ECC_P384_SIZE_IN_BYTES; + break; + case ECC_BRAINPOOLP512R1: + type = (u32)XASU_ECC_BRAINPOOL_P512; + len = (u32)XASU_ECC_P512_SIZE_IN_BYTES; + break; +#endif + default: + return CRYPTOCB_UNAVAILABLE; + } + if ((u32)key->dp->size != len) { + return CRYPTOCB_UNAVAILABLE; + } + *curveType = type; + *keyLen = len; + return 0; +} + +/* Full-hardware ECDH shared-secret generation. Both keys must be on the same + * supported curve; the ASU multiplies our private scalar by the peer point and + * returns the curve-width X coordinate. *outlen is set to the curve width. A hardware + * failure is surfaced as WC_HW_E; only unsupported inputs decline to software. */ +int wc_AsuEcdh(wc_CryptoInfo* info) +{ + WC_DECLARE_VAR(req, AsuEcdhReq, 1, NULL); + ecc_key* priv; + ecc_key* pub; + u32 curveType = 0; + u32 keyLen = 0; + word32 status; + word32 addl = 0; + int ret = 0; + + if (info == NULL) { + return BAD_FUNC_ARG; + } + if (info->algo_type != WC_ALGO_TYPE_PK || + info->pk.type != WC_PK_TYPE_ECDH) { + return CRYPTOCB_UNAVAILABLE; + } + + priv = info->pk.ecdh.private_key; + pub = info->pk.ecdh.public_key; + + if (priv == NULL || pub == NULL || info->pk.ecdh.out == NULL || + info->pk.ecdh.outlen == NULL) { + return BAD_FUNC_ARG; + } + + ret = wc_AsuEcdhCurve(priv, &curveType, &keyLen); + if (ret != 0) { + return ret; + } + /* Both keys must be on the same supported curve. */ + if (pub->dp == NULL || pub->dp->id != priv->dp->id) { + return CRYPTOCB_UNAVAILABLE; + } + /* Our private scalar is required and must fit the curve width. */ + if (mp_iszero(priv->k) || mp_unsigned_bin_size(priv->k) > (int)keyLen) { + return CRYPTOCB_UNAVAILABLE; + } + if (*info->pk.ecdh.outlen < keyLen) { + return CRYPTOCB_UNAVAILABLE; + } + + WC_ALLOC_VAR_EX(req, AsuEcdhReq, 1, NULL, DYNAMIC_TYPE_TMP_BUFFER, + ret = MEMORY_E); + if (!WC_VAR_OK(req)) { + return ret; + } + + XMEMSET(req, 0, sizeof(*req)); + /* Our private scalar d, and the peer public point Qx||Qy, each curve-width. */ + if (mp_to_unsigned_bin_len(priv->k, req->privKey, (int)keyLen) < 0 || + mp_to_unsigned_bin_len(pub->pubkey.x, req->pubKey, (int)keyLen) < 0 || + mp_to_unsigned_bin_len(pub->pubkey.y, req->pubKey + keyLen, + (int)keyLen) < 0) { + ret = WC_HW_E; + goto out; + } + + req->params.CurveType = curveType; + req->params.KeyLen = keyLen; + req->params.PvtKeyAddr = (u64)(UINTPTR)req->privKey; + req->params.PubKeyAddr = (u64)(UINTPTR)req->pubKey; + req->params.SharedSecretAddr = (u64)(UINTPTR)req->secret; + req->params.SharedSecretObjIdAddr = 0; + + WC_ASU_PRINTF("[ASU] ecdh curve=%u keyLen=%u\r\n", + (unsigned int)curveType, (unsigned int)keyLen); + + wc_AsuCacheFlush(req->privKey, keyLen); + wc_AsuCacheFlush(req->pubKey, 2U * keyLen); + /* Clean the output too: without this its dirty (zeroed) cache lines can be + * written back over the DMA'd shared secret, so the CPU reads stale zeros. */ + wc_AsuCacheFlush(req->secret, keyLen); + + status = wc_AsuTransact(wc_AsuEcdhSubmit, req, &addl); + + wc_AsuCacheInvalidate(req->secret, keyLen); + + WC_ASU_PRINTF("[ASU] ecdh st=%u addl=0x%x\r\n", + (unsigned int)status, (unsigned int)addl); + + if (status != XST_SUCCESS) { + /* Inputs were validated; a failure here is a real hardware fault. */ + ret = WC_HW_E; + goto out; + } + XMEMCPY(info->pk.ecdh.out, req->secret, keyLen); + *info->pk.ecdh.outlen = keyLen; + ret = 0; + +out: + WC_FREE_VAR_EX(req, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return ret; +} + +#endif /* WOLFSSL_VERSAL_GEN2_ASU_ECDH */ diff --git a/wolfcrypt/src/port/xilinx/versal_gen2_asu/asu_ecies.c b/wolfcrypt/src/port/xilinx/versal_gen2_asu/asu_ecies.c new file mode 100644 index 00000000000..e63e89a2599 --- /dev/null +++ b/wolfcrypt/src/port/xilinx/versal_gen2_asu/asu_ecies.c @@ -0,0 +1,513 @@ +/* asu_ecies.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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 + */ + +/* ASU ECIES offload for the wolfSSL crypto callback. + * + * wolfSSL's ECIES with the AES-GCM DEM is: ECDH -> HKDF(shared secret, salt, info) + * -> one AES key -> AES-GCM (fresh 12-byte nonce, 16-byte tag), output packed as + * [ephemeral pubkey (X9.63 uncompressed) || nonce || ciphertext || tag]. The ASU + * XEcies engine performs exactly this in one command, so the offload marshals the + * keys and scheme parameters, runs XAsu_EciesEncrypt/Decrypt, and reassembles the + * output in wolfSSL's layout. + * + * The ASU always generates its own ephemeral keypair on encrypt (wolfSSL's supplied + * ephemeral private key is unused); the ASU's ephemeral public key is emitted in the + * output and the peer decrypts against it, so the result is self-consistent. Only the + * default GCM scheme (AES-128/256-GCM + HKDF-SHA256, shared-secret-only, uncompressed + * ephemeral key) is offloaded; anything else declines to software. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#ifdef WOLFSSL_VERSAL_GEN2_ASU_ECIES + +#include +#include +#include +#include +#include +#include + +#include "xasu_ecies.h" +#include "xasu_eciesinfo.h" +#include "xasu_eccinfo.h" +#include "xasu_shainfo.h" +#include "xasu_aesinfo.h" +#include "xstatus.h" + +#if defined(NO_ECC) || !defined(HAVE_ECC) + #error "WOLFSSL_VERSAL_GEN2_ASU_ECIES requires ECC" +#endif +#ifndef HAVE_ECC_ENCRYPT + #error "WOLFSSL_VERSAL_GEN2_ASU_ECIES requires HAVE_ECC_ENCRYPT" +#endif +#if defined(NO_AES) || !defined(HAVE_AESGCM) + #error "WOLFSSL_VERSAL_GEN2_ASU_ECIES requires AES-GCM" +#endif + +/* wolfSSL ecEncAlgo AES-GCM values and the ECIES DEM sizes. */ +#define WC_ASU_ECIES_AES128_GCM 5 +#define WC_ASU_ECIES_AES256_GCM 6 +#define WC_ASU_ECIES_HKDF_SHA256 1 /* wolfSSL ecKdfAlgo default */ +#define WC_ASU_ECIES_NONCE_SZ 12 +#define WC_ASU_ECIES_TAG_SZ 16 + +/* Largest supported curve width (NIST P-384, 48 bytes; P-521 is not offloaded). */ +#define WC_ASU_ECIES_MAX_KEYLEN XASU_ECC_P384_SIZE_IN_BYTES + +/* One ASU ECIES request. The plaintext/ciphertext live in the caller's buffers + * (variable length, addressed directly); the fixed-size key/nonce/tag records are + * heap-resident here so the ASU DMA can reach them. */ +typedef struct { + XAsu_EciesParams params; + byte rxKey[2U * WC_ASU_ECIES_MAX_KEYLEN]; /* peer pub Qx||Qy (enc) or priv d (dec) */ + byte txKey[2U * WC_ASU_ECIES_MAX_KEYLEN]; /* ephemeral pub Qx||Qy (enc out/dec in) */ + byte iv[WC_ASU_ECIES_NONCE_SZ]; /* GCM nonce */ + byte tag[WC_ASU_ECIES_TAG_SZ]; /* GCM tag */ +} AsuEciesReq; + +/* Submit thunk: queue one ASU ECIES operation. IsEncrypt picks the command. */ +typedef struct { + AsuEciesReq* req; + int isEncrypt; +} AsuEciesSubmitCtx; + +static int wc_AsuEciesSubmit(XAsu_ClientParams* params, void* ctx) +{ + AsuEciesSubmitCtx* sc = (AsuEciesSubmitCtx*)ctx; + + if (params == NULL || sc == NULL || sc->req == NULL) { + return XST_FAILURE; + } + if (sc->isEncrypt != 0) { + return XAsu_EciesEncrypt(params, &sc->req->params); + } + return XAsu_EciesDecrypt(params, &sc->req->params); +} + +/* Map the wolfSSL curve id to an ASU CurveType and byte width for the ECIES-capable + * prime curves, declining others so wolfSSL falls back to software. */ +static int wc_AsuEciesCurve(ecc_key* key, u8* curveType, u8* keyLen) +{ + if (key == NULL || curveType == NULL || keyLen == NULL) { + return BAD_FUNC_ARG; + } + if (key->dp == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + switch (key->dp->id) { + case ECC_SECP256R1: + *curveType = (u8)XASU_ECC_NIST_P256; + *keyLen = (u8)XASU_ECC_P256_SIZE_IN_BYTES; + break; + case ECC_SECP384R1: + *curveType = (u8)XASU_ECC_NIST_P384; + *keyLen = (u8)XASU_ECC_P384_SIZE_IN_BYTES; + break; +#ifdef HAVE_ECC_BRAINPOOL + case ECC_BRAINPOOLP256R1: + *curveType = (u8)XASU_ECC_BRAINPOOL_P256; + *keyLen = (u8)XASU_ECC_P256_SIZE_IN_BYTES; + break; + case ECC_BRAINPOOLP384R1: + *curveType = (u8)XASU_ECC_BRAINPOOL_P384; + *keyLen = (u8)XASU_ECC_P384_SIZE_IN_BYTES; + break; +#endif + default: + return CRYPTOCB_UNAVAILABLE; + } + return 0; +} + +/* Read the ECIES scheme from the ctx. Only AES-128/256-GCM with HKDF-SHA256 is + * offloaded; anything else declines. Fills the ASU AES key size and SHA type/mode. */ +static int wc_AsuEciesScheme(ecEncCtx* ctx, u8* aesKeySize, u8* shaType, u8* shaMode) +{ + byte encAlgo = 0; + byte kdfAlgo = 0; + + if (ctx == NULL || aesKeySize == NULL || shaType == NULL || shaMode == NULL) { + return BAD_FUNC_ARG; + } + if (wc_ecc_ctx_get_algo(ctx, &encAlgo, &kdfAlgo, NULL) != 0) { + return CRYPTOCB_UNAVAILABLE; + } + if (kdfAlgo != WC_ASU_ECIES_HKDF_SHA256) { + return CRYPTOCB_UNAVAILABLE; + } + if (encAlgo == WC_ASU_ECIES_AES128_GCM) { + *aesKeySize = (u8)XASU_AES_KEY_SIZE_128_BITS; + } + else if (encAlgo == WC_ASU_ECIES_AES256_GCM) { + *aesKeySize = (u8)XASU_AES_KEY_SIZE_256_BITS; + } + else { + return CRYPTOCB_UNAVAILABLE; + } + *shaType = (u8)XASU_SHA2_TYPE; + *shaMode = (u8)XASU_SHA_MODE_256; + return 0; +} + +/* Marshal an ecc_key public point into Qx||Qy, big-endian fixed width. */ +static int wc_AsuEciesExportPub(ecc_key* key, byte* out, u8 keyLen) +{ + if (mp_to_unsigned_bin_len(key->pubkey.x, out, (int)keyLen) < 0) { + return WC_HW_E; + } + if (mp_to_unsigned_bin_len(key->pubkey.y, out + keyLen, (int)keyLen) < 0) { + return WC_HW_E; + } + return 0; +} + +/* Fill the common ECIES params (scheme, salt/info, lengths) shared by enc/dec. */ +static void wc_AsuEciesFillParams(XAsu_EciesParams* p, ecEncCtx* ctx, u8 curveType, + u8 keyLen, u8 aesKeySize, u8 shaType, u8 shaMode, word32 dataLen) +{ + word32 saltLen = 0; + word32 infoLen = 0; + const byte* salt = NULL; + const byte* info = NULL; + + (void)wc_ecc_ctx_get_kdf_salt(ctx, &salt, &saltLen); + (void)wc_ecc_ctx_get_info(ctx, &info, &infoLen); + + p->EccCurveType = curveType; + p->EccKeyLength = keyLen; + p->ShaType = shaType; + p->ShaMode = shaMode; + p->AesKeySize = aesKeySize; + p->IvLength = (u8)WC_ASU_ECIES_NONCE_SZ; + p->MacLength = (u8)WC_ASU_ECIES_TAG_SZ; + p->DataLength = dataLen; + p->SaltAddr = (u64)(UINTPTR)salt; + p->SaltLen = saltLen; + p->ContextAddr = (u64)(UINTPTR)info; + p->ContextLen = infoLen; +} + +/* ECIES encrypt: peer public key + plaintext -> [ephemeral||nonce||ct||tag]. The + * ASU generates the ephemeral keypair, derives the key, and GCM-encrypts. */ +static int wc_AsuEciesEncrypt(wc_CryptoInfo* info) +{ + WC_DECLARE_VAR(req, AsuEciesReq, 1, NULL); + AsuEciesSubmitCtx sc; + ecc_key* privKey = info->pk.eciesencrypt.privKey; /* ephemeral, carries the RNG */ + ecc_key* pubKey = info->pk.eciesencrypt.pubKey; + const byte* msg = info->pk.eciesencrypt.msg; + word32 msgSz = info->pk.eciesencrypt.msgSz; + byte* out = info->pk.eciesencrypt.out; + ecEncCtx* ctx = info->pk.eciesencrypt.ctx; + WC_RNG* rng = NULL; + u8 curveType = 0; + u8 keyLen = 0; + u8 aesKeySize = 0; + u8 shaType = 0; + u8 shaMode = 0; + word32 pubKeySz; + word32 need; + word32 status; + word32 addl = 0; + int ret; + + /* Decline (fall back to software) for anything not fully handled; a hard error + * from the callback would abort the op instead of falling back. */ + if (pubKey == NULL || msg == NULL || out == NULL || + info->pk.eciesencrypt.outSz == NULL || ctx == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + /* The ASU emits an uncompressed ephemeral key; decline a compressed request. */ + if (info->pk.eciesencrypt.compressed != 0) { + return CRYPTOCB_UNAVAILABLE; + } + ret = wc_AsuEciesCurve(pubKey, &curveType, &keyLen); + if (ret != 0) { + return ret; + } + ret = wc_AsuEciesScheme(ctx, &aesKeySize, &shaType, &shaMode); + if (ret != 0) { + return ret; + } + /* Only GCM reaches here (other schemes already declined to software). + * AAD: REQ_RESP binds macSalt as GCM AAD, which the ASU has no input for. This + * is a legitimate wolfSSL scheme (wolfcrypt's own tests use it), so decline and + * let software handle it. Context: the caller opted into HW GCM without an AAD + * but left the KDF context empty, which the ASU firmware requires; that is a + * usage error for the HW path, so reject it with BAD_FUNC_ARG. */ + { + const byte* macSalt = NULL; + const byte* infoP = NULL; + word32 macSaltSz = 0; + word32 infoSz = 0; + (void)wc_ecc_ctx_get_mac_salt(ctx, &macSalt, &macSaltSz); + (void)wc_ecc_ctx_get_info(ctx, &infoP, &infoSz); + if (macSaltSz > 0U) { + return CRYPTOCB_UNAVAILABLE; + } + if (infoSz == 0U) { + WC_ASU_PRINTF("[ASU] ecies: GCM needs a non-empty KDF context\r\n"); + return BAD_FUNC_ARG; + } + } + /* A random nonce is needed; the ephemeral key carries the RNG. Decline if none. */ + if (privKey == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + rng = privKey->rng; + if (rng == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + + pubKeySz = 1U + (2U * (word32)keyLen); /* X9.63 uncompressed 0x04||Qx||Qy */ + need = pubKeySz + (word32)WC_ASU_ECIES_NONCE_SZ + msgSz + + (word32)WC_ASU_ECIES_TAG_SZ; + if (*info->pk.eciesencrypt.outSz < need) { + return BUFFER_E; + } + + WC_ALLOC_VAR_EX(req, AsuEciesReq, 1, NULL, DYNAMIC_TYPE_TMP_BUFFER, + ret = MEMORY_E); + if (!WC_VAR_OK(req)) { + return ret; + } + XMEMSET(req, 0, sizeof(*req)); + + ret = wc_AsuEciesExportPub(pubKey, req->rxKey, keyLen); + if (ret != 0) { + goto out; + } + ret = wc_RNG_GenerateBlock(rng, req->iv, WC_ASU_ECIES_NONCE_SZ); + if (ret != 0) { + goto out; + } + + wc_AsuEciesFillParams(&req->params, ctx, curveType, keyLen, aesKeySize, + shaType, shaMode, msgSz); + req->params.RxKeyAddr = (u64)(UINTPTR)req->rxKey; /* peer public key in */ + req->params.TxKeyAddr = (u64)(UINTPTR)req->txKey; /* ephemeral public key out */ + req->params.IvAddr = (u64)(UINTPTR)req->iv; + req->params.MacAddr = (u64)(UINTPTR)req->tag; /* GCM tag out */ + req->params.InDataAddr = (u64)(UINTPTR)msg; /* plaintext in */ + req->params.OutDataAddr = (u64)(UINTPTR)(out + pubKeySz + + (word32)WC_ASU_ECIES_NONCE_SZ); /* ciphertext out, in place */ + + WC_ASU_PRINTF("[ASU] ecies enc curve=%u keyLen=%u aesKey=%u msgSz=%u\r\n", + (unsigned int)curveType, (unsigned int)keyLen, (unsigned int)aesKeySize, + (unsigned int)msgSz); + + /* Clean inputs out to memory; invalidate the DMA-written regions after. */ + wc_AsuCacheFlush(req, sizeof(*req)); + wc_AsuCacheFlush(msg, msgSz); + if (req->params.SaltLen != 0) { + wc_AsuCacheFlush((const void*)(UINTPTR)req->params.SaltAddr, + req->params.SaltLen); + } + if (req->params.ContextLen != 0) { + wc_AsuCacheFlush((const void*)(UINTPTR)req->params.ContextAddr, + req->params.ContextLen); + } + wc_AsuCacheFlush(out, need); + + sc.req = req; + sc.isEncrypt = 1; + status = wc_AsuTransact(wc_AsuEciesSubmit, &sc, &addl); + + wc_AsuCacheInvalidate(req, sizeof(*req)); + wc_AsuCacheInvalidate(out, need); + + if (status != XST_SUCCESS) { + ret = WC_HW_E; + goto out; + } + + /* Assemble wolfSSL's layout: 0x04 || ephemeral Qx||Qy || nonce || ct || tag. + * The ciphertext is already in place; place the ephemeral key, nonce, tag. */ + out[0] = (byte)ECC_POINT_UNCOMP; + XMEMCPY(out + 1, req->txKey, 2U * (word32)keyLen); + XMEMCPY(out + pubKeySz, req->iv, WC_ASU_ECIES_NONCE_SZ); + XMEMCPY(out + pubKeySz + (word32)WC_ASU_ECIES_NONCE_SZ + msgSz, req->tag, + WC_ASU_ECIES_TAG_SZ); + *info->pk.eciesencrypt.outSz = need; + ret = 0; + +out: + WC_FREE_VAR_EX(req, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return ret; +} + +/* ECIES decrypt: [ephemeral||nonce||ct||tag] + own private key -> plaintext. The + * ASU derives the key from ECDH(own priv, ephemeral pub) and GCM-decrypts/verifies. */ +static int wc_AsuEciesDecrypt(wc_CryptoInfo* info) +{ + WC_DECLARE_VAR(req, AsuEciesReq, 1, NULL); + AsuEciesSubmitCtx sc; + ecc_key* privKey = info->pk.eciesdecrypt.privKey; + const byte* msg = info->pk.eciesdecrypt.msg; + word32 msgSz = info->pk.eciesdecrypt.msgSz; + byte* out = info->pk.eciesdecrypt.out; + ecEncCtx* ctx = info->pk.eciesdecrypt.ctx; + u8 curveType = 0; + u8 keyLen = 0; + u8 aesKeySize = 0; + u8 shaType = 0; + u8 shaMode = 0; + word32 pubKeySz; + word32 ctLen; + word32 status; + word32 addl = 0; + int ret; + + /* Decline (fall back to software) for anything not fully handled. */ + if (privKey == NULL || msg == NULL || out == NULL || + info->pk.eciesdecrypt.outSz == NULL || ctx == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + if (mp_iszero(privKey->k) == MP_YES) { + return CRYPTOCB_UNAVAILABLE; + } + ret = wc_AsuEciesCurve(privKey, &curveType, &keyLen); + if (ret != 0) { + return ret; + } + ret = wc_AsuEciesScheme(ctx, &aesKeySize, &shaType, &shaMode); + if (ret != 0) { + return ret; + } + /* Only GCM reaches here (other schemes already declined to software). + * AAD: REQ_RESP binds macSalt as GCM AAD, which the ASU has no input for. This + * is a legitimate wolfSSL scheme (wolfcrypt's own tests use it), so decline and + * let software handle it. Context: the caller opted into HW GCM without an AAD + * but left the KDF context empty, which the ASU firmware requires; that is a + * usage error for the HW path, so reject it with BAD_FUNC_ARG. */ + { + const byte* macSalt = NULL; + const byte* infoP = NULL; + word32 macSaltSz = 0; + word32 infoSz = 0; + (void)wc_ecc_ctx_get_mac_salt(ctx, &macSalt, &macSaltSz); + (void)wc_ecc_ctx_get_info(ctx, &infoP, &infoSz); + if (macSaltSz > 0U) { + return CRYPTOCB_UNAVAILABLE; + } + if (infoSz == 0U) { + WC_ASU_PRINTF("[ASU] ecies: GCM needs a non-empty KDF context\r\n"); + return BAD_FUNC_ARG; + } + } + + /* The ephemeral key is uncompressed X9.63; decline compressed. */ + if (msgSz < 1U || msg[0] != (byte)ECC_POINT_UNCOMP) { + return CRYPTOCB_UNAVAILABLE; + } + pubKeySz = 1U + (2U * (word32)keyLen); + if (msgSz < pubKeySz + (word32)WC_ASU_ECIES_NONCE_SZ + + (word32)WC_ASU_ECIES_TAG_SZ) { + return BAD_FUNC_ARG; + } + ctLen = msgSz - pubKeySz - (word32)WC_ASU_ECIES_NONCE_SZ - + (word32)WC_ASU_ECIES_TAG_SZ; + if (*info->pk.eciesdecrypt.outSz < ctLen) { + return BUFFER_E; + } + + WC_ALLOC_VAR_EX(req, AsuEciesReq, 1, NULL, DYNAMIC_TYPE_TMP_BUFFER, + ret = MEMORY_E); + if (!WC_VAR_OK(req)) { + return ret; + } + XMEMSET(req, 0, sizeof(*req)); + + /* Our private scalar; the ephemeral public key comes straight from the blob. */ + if (mp_to_unsigned_bin_len(privKey->k, req->rxKey, (int)keyLen) < 0) { + ret = WC_HW_E; + goto out; + } + XMEMCPY(req->txKey, msg + 1, 2U * (word32)keyLen); + XMEMCPY(req->iv, msg + pubKeySz, WC_ASU_ECIES_NONCE_SZ); + XMEMCPY(req->tag, msg + msgSz - (word32)WC_ASU_ECIES_TAG_SZ, + WC_ASU_ECIES_TAG_SZ); + + wc_AsuEciesFillParams(&req->params, ctx, curveType, keyLen, aesKeySize, + shaType, shaMode, ctLen); + req->params.RxKeyAddr = (u64)(UINTPTR)req->rxKey; /* our private key in */ + req->params.TxKeyAddr = (u64)(UINTPTR)req->txKey; /* ephemeral public key in */ + req->params.IvAddr = (u64)(UINTPTR)req->iv; + req->params.MacAddr = (u64)(UINTPTR)req->tag; /* GCM tag in */ + req->params.InDataAddr = (u64)(UINTPTR)(msg + pubKeySz + + (word32)WC_ASU_ECIES_NONCE_SZ); /* ciphertext in */ + req->params.OutDataAddr = (u64)(UINTPTR)out; /* plaintext out */ + + WC_ASU_PRINTF("[ASU] ecies dec curve=%u keyLen=%u aesKey=%u ctLen=%u\r\n", + (unsigned int)curveType, (unsigned int)keyLen, (unsigned int)aesKeySize, + (unsigned int)ctLen); + + wc_AsuCacheFlush(req, sizeof(*req)); + wc_AsuCacheFlush(msg, msgSz); + if (req->params.SaltLen != 0) { + wc_AsuCacheFlush((const void*)(UINTPTR)req->params.SaltAddr, + req->params.SaltLen); + } + if (req->params.ContextLen != 0) { + wc_AsuCacheFlush((const void*)(UINTPTR)req->params.ContextAddr, + req->params.ContextLen); + } + wc_AsuCacheFlush(out, ctLen); + + sc.req = req; + sc.isEncrypt = 0; + status = wc_AsuTransact(wc_AsuEciesSubmit, &sc, &addl); + + wc_AsuCacheInvalidate(out, ctLen); + + if (status != XST_SUCCESS) { + ret = WC_HW_E; + goto out; + } + *info->pk.eciesdecrypt.outSz = ctLen; + ret = 0; + +out: + WC_FREE_VAR_EX(req, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return ret; +} + +/* Single entry point for the ECIES engine, reached from the PK dispatcher. */ +int wc_AsuEcies(wc_CryptoInfo* info) +{ + if (info == NULL) { + return BAD_FUNC_ARG; + } + if (info->pk.type == WC_PK_TYPE_ECIES_ENCRYPT) { + return wc_AsuEciesEncrypt(info); + } + if (info->pk.type == WC_PK_TYPE_ECIES_DECRYPT) { + return wc_AsuEciesDecrypt(info); + } + return CRYPTOCB_UNAVAILABLE; +} + +#endif /* WOLFSSL_VERSAL_GEN2_ASU_ECIES */ diff --git a/wolfssl/wolfcrypt/ecc.h b/wolfssl/wolfcrypt/ecc.h index d020523c09f..a9bd1652dbd 100644 --- a/wolfssl/wolfcrypt/ecc.h +++ b/wolfssl/wolfcrypt/ecc.h @@ -1098,6 +1098,8 @@ WOLFSSL_API int wc_ecc_ctx_get_kdf_salt(ecEncCtx* ctx, const byte** salt, word32* sz); WOLFSSL_API int wc_ecc_ctx_get_info(ecEncCtx* ctx, const byte** info, word32* sz); +WOLFSSL_API +int wc_ecc_ctx_get_mac_salt(ecEncCtx* ctx, const byte** salt, word32* sz); #endif /* WOLF_CRYPTO_CB */ WOLFSSL_API const byte* wc_ecc_ctx_get_own_salt(ecEncCtx* ctx); diff --git a/wolfssl/wolfcrypt/port/xilinx/versal_gen2_asu/asu_ecc.h b/wolfssl/wolfcrypt/port/xilinx/versal_gen2_asu/asu_ecc.h new file mode 100644 index 00000000000..30db799a8dd --- /dev/null +++ b/wolfssl/wolfcrypt/port/xilinx/versal_gen2_asu/asu_ecc.h @@ -0,0 +1,54 @@ +/* asu_ecc.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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 + */ + +/* ASU ECDSA offload for the wolfSSL crypto callback. wolfSSL's ECDSA sign and + * verify callbacks route here; the ASU performs the elliptic-curve operation on + * the supplied digest. Keys and r||s are marshalled big-endian, fixed-width into + * the ASU structs, and the DER<->raw signature conversion uses wolfSSL helpers. + * NIST P-192/256/384/521 and Brainpool P-256/320/384/512; other curves decline to + * software. Plain Ed25519 and Ed448 sign/verify also route here (ASU hashes the + * message). See asu_ecc.c. */ + +#ifndef WOLFSSL_VERSAL_GEN2_ASU_ECC_H +#define WOLFSSL_VERSAL_GEN2_ASU_ECC_H + +#include + +#ifdef WOLFSSL_VERSAL_GEN2_ASU_ECC + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Single entry point for the ECC engine (WC_ALGO_TYPE_PK). Dispatches ECDSA sign + * and verify to the matching ASU command for the supported NIST curves. Returns 0 + * on success, CRYPTOCB_UNAVAILABLE to defer to software, or a negative error. */ +WOLFSSL_LOCAL int wc_AsuEcc(wc_CryptoInfo* info); + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSSL_VERSAL_GEN2_ASU_ECC */ + +#endif /* WOLFSSL_VERSAL_GEN2_ASU_ECC_H */ diff --git a/wolfssl/wolfcrypt/port/xilinx/versal_gen2_asu/asu_ecdh.h b/wolfssl/wolfcrypt/port/xilinx/versal_gen2_asu/asu_ecdh.h new file mode 100644 index 00000000000..a72d1d19762 --- /dev/null +++ b/wolfssl/wolfcrypt/port/xilinx/versal_gen2_asu/asu_ecdh.h @@ -0,0 +1,52 @@ +/* asu_ecdh.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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 + */ + +/* ASU ECDH shared-secret offload for the wolfSSL crypto callback. wolfSSL's ECDH + * callback (wc_ecc_shared_secret) routes here; the ASU computes the X9.63 shared + * secret (the X coordinate of private_key->k * public_key->pubkey) on the supported + * NIST/Brainpool prime curves. Keys are marshalled big-endian, fixed-width into the + * ASU structs; other curves decline to software. See asu_ecdh.c. */ + +#ifndef WOLFSSL_VERSAL_GEN2_ASU_ECDH_H +#define WOLFSSL_VERSAL_GEN2_ASU_ECDH_H + +#include + +#ifdef WOLFSSL_VERSAL_GEN2_ASU_ECDH + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Single entry point for the ECDH engine (WC_ALGO_TYPE_PK / WC_PK_TYPE_ECDH). + * Computes the shared secret on the ASU for the supported curves. Returns 0 on + * success, CRYPTOCB_UNAVAILABLE to defer to software, or a negative error. */ +WOLFSSL_LOCAL int wc_AsuEcdh(wc_CryptoInfo* info); + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSSL_VERSAL_GEN2_ASU_ECDH */ + +#endif /* WOLFSSL_VERSAL_GEN2_ASU_ECDH_H */ diff --git a/wolfssl/wolfcrypt/port/xilinx/versal_gen2_asu/asu_ecies.h b/wolfssl/wolfcrypt/port/xilinx/versal_gen2_asu/asu_ecies.h new file mode 100644 index 00000000000..14a11f7afd7 --- /dev/null +++ b/wolfssl/wolfcrypt/port/xilinx/versal_gen2_asu/asu_ecies.h @@ -0,0 +1,50 @@ +/* asu_ecies.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL 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. + * + * wolfSSL 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 + */ + +/* ASU ECIES offload for the wolfSSL crypto callback: ECDH -> HKDF -> AES-GCM, + * matching wolfSSL's AES-GCM DEM ECIES scheme. See asu_ecies.c. */ + +#ifndef WOLFSSL_VERSAL_GEN2_ASU_ECIES_H +#define WOLFSSL_VERSAL_GEN2_ASU_ECIES_H + +#include + +#ifdef WOLFSSL_VERSAL_GEN2_ASU_ECIES + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Single entry point for the ECIES engine. The crypto callback dispatcher routes + * WC_PK_TYPE_ECIES_ENCRYPT / _DECRYPT here. Returns 0 on success, + * CRYPTOCB_UNAVAILABLE for an unsupported scheme or curve (software fallback), or + * a negative error. */ +WOLFSSL_LOCAL int wc_AsuEcies(wc_CryptoInfo* info); + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSSL_VERSAL_GEN2_ASU_ECIES */ + +#endif /* WOLFSSL_VERSAL_GEN2_ASU_ECIES_H */