* [PATCH] examples: add Wycheproof validation app
@ 2026-09-15 15:51 Kai Ji
2026-09-15 16:58 ` Stephen Hemminger
` (2 more replies)
0 siblings, 3 replies; 17+ messages in thread
From: Kai Ji @ 2026-09-15 15:51 UTC (permalink / raw)
To: dev; +Cc: Kai Ji
Add a Wycheproof JSON vector validation example for cryptodev PMDs.
Support these algorithms when advertised by the selected PMD:
- AEAD: AES-GCM, AES-CCM, SM4-GCM, ChaCha20-Poly1305
- MAC: AES-CMAC, AES-GMAC, HMAC SHA-1/SHA-2/SHA-3/SM3
- Asymmetric: DSA (P1363 verify), ECDSA (P1363 verify),
ECDH (ecpoint shared-secret compute)
Validate valid vectors against generated ciphertexts, tags, plaintexts,
digests, shared secrets, or signature verification, and require the
expected rejection for invalid vectors. Digest inputs for DSA and ECDSA
use the symmetric auth path, selecting a separate symmetric-capable
device when the target device is asymmetric-only.
Skip parameter combinations outside PMD capability ranges and identify
recognized vector families without a compatible DPDK transform. A
--debug option lists every failed or skipped vector.
Add Meson and standalone build integration, with usage documentation.
Signed-off-by: Kai Ji <kai.ji@intel.com>
---
examples/meson.build | 1 +
examples/wycheproof_validation/Makefile | 40 +
examples/wycheproof_validation/README.md | 124 ++
examples/wycheproof_validation/main.c | 1882 ++++++++++++++++++++
examples/wycheproof_validation/meson.build | 17 +
5 files changed, 2064 insertions(+)
create mode 100644 examples/wycheproof_validation/Makefile
create mode 100644 examples/wycheproof_validation/README.md
create mode 100644 examples/wycheproof_validation/main.c
create mode 100644 examples/wycheproof_validation/meson.build
diff --git a/examples/meson.build b/examples/meson.build
index 25d9c88457..000d05863a 100644
--- a/examples/meson.build
+++ b/examples/meson.build
@@ -59,6 +59,7 @@ all_examples = [
'vm_power_manager/guest_cli',
'vmdq',
'vmdq_dcb',
+ 'wycheproof_validation',
]
# on install, skip copying all meson.build files
diff --git a/examples/wycheproof_validation/Makefile b/examples/wycheproof_validation/Makefile
new file mode 100644
index 0000000000..c687282465
--- /dev/null
+++ b/examples/wycheproof_validation/Makefile
@@ -0,0 +1,40 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026
+
+APP = wycheproof_validation
+SRCS-y := main.c
+
+PKGCONF ?= pkg-config
+
+ifneq ($(shell $(PKGCONF) --exists libdpdk && echo 0),0)
+$(error "no installation of DPDK found")
+endif
+ifneq ($(shell $(PKGCONF) --exists jansson && echo 0),0)
+$(error "Jansson is required")
+endif
+
+all: shared
+.PHONY: shared static
+shared: build/$(APP)-shared
+ ln -sf $(APP)-shared build/$(APP)
+static: build/$(APP)-static
+ ln -sf $(APP)-static build/$(APP)
+
+PC_FILE := $(shell $(PKGCONF) --path libdpdk 2>/dev/null)
+CFLAGS += -O3 $(shell $(PKGCONF) --cflags libdpdk jansson)
+LDFLAGS_SHARED = $(shell $(PKGCONF) --libs libdpdk jansson)
+LDFLAGS_STATIC = $(shell $(PKGCONF) --static --libs libdpdk jansson)
+
+build/$(APP)-shared: $(SRCS-y) Makefile $(PC_FILE) | build
+ $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_SHARED)
+
+build/$(APP)-static: $(SRCS-y) Makefile $(PC_FILE) | build
+ $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_STATIC)
+
+build:
+ @mkdir -p $@
+
+.PHONY: clean
+clean:
+ rm -f build/$(APP) build/$(APP)-static build/$(APP)-shared
+ test -d build && rmdir -p build || true
\ No newline at end of file
diff --git a/examples/wycheproof_validation/README.md b/examples/wycheproof_validation/README.md
new file mode 100644
index 0000000000..7782e1aab5
--- /dev/null
+++ b/examples/wycheproof_validation/README.md
@@ -0,0 +1,124 @@
+# Wycheproof Validation
+
+`dpdk-wycheproof_validation` validates DPDK cryptodev PMDs against Google
+Wycheproof JSON vectors. It reads vector files directly at runtime; no copied
+or generated vector corpus is kept in DPDK.
+
+The initial implementation supports `AES-GCM`, `AES-CCM`,
+`CHACHA20-POLY1305`, and `SM4-GCM` using `aead_test_schema_v1.json`, plus
+HMAC-SHA1/224/256/384/512, HMAC-SHA3-224/256/384/512, HMAC-SM3, and AES-CMAC
+using `mac_test_schema_v1.json`. For each compatible AEAD vector it maps:
+
+| Wycheproof | DPDK AEAD operation |
+|---|---|
+| `key` | AEAD key |
+| `iv` | AEAD IV |
+| `aad` | AEAD associated data |
+| `msg` | encryption input / expected decryption output |
+| `ct` | expected encryption output / decryption input |
+| `tag` | expected encryption tag / decryption digest |
+
+`valid` vectors must encrypt and decrypt successfully with exact matching
+ciphertext, tag, and plaintext. `invalid` vectors run decrypt only and must
+complete with `RTE_CRYPTO_OP_STATUS_AUTH_FAILED`. The application reports
+`acceptable` vectors as skipped until an algorithm-specific policy is added.
+Vectors outside a PMD's advertised AEAD capability ranges are skipped and
+counted separately.
+
+For HMAC and AES-CMAC, `key`, `msg`, and `tag` map to the DPDK authentication
+key, input data, and digest buffer. Valid vectors require exact generated tags;
+invalid vectors require `RTE_CRYPTO_OP_STATUS_AUTH_FAILED` during verification.
+
+AES-GMAC uses `mac_with_iv_test_schema_v1.json`; its `iv` maps to the DPDK
+authentication IV in addition to the MAC fields above.
+
+AES-CCM uses the DPDK AEAD API's CCM-specific layout: the nonce is stored one
+byte after the IV pointer and the AAD is placed after its required 18-byte
+prefix.
+
+DSA verification is supported for the `dsa_p1363_verify_schema_v1.json` schema.
+The group `p`, `q`, `g`, and `y` map to `rte_crypto_dsa_xform` and the verify
+operation's public key; the per-test `msg` is hashed with the group `sha` via
+the cryptodev auth path, and the fixed-width P1363 `sig` is split into `r` and
+`s`. `valid` vectors must verify (`RTE_CRYPTO_OP_STATUS_SUCCESS`); `invalid`
+vectors must not. This path needs a PMD advertising the asymmetric DSA verify
+capability (for example `crypto_openssl`).
+
+ECDH shared-secret computation is supported for the
+`ecdh_ecpoint_test_schema_v1.json` schema. The group `curve` selects the DPDK
+EC group; the per-test uncompressed `public` point (`0x04 || x || y`) maps to
+`rte_crypto_ec_point`, `private` is the scalar, and the derived shared secret's
+x-coordinate is compared against `shared`. `valid` vectors must produce the
+expected secret; `invalid` vectors must not. Invalid-curve vectors (empty
+`shared`) are skipped because on-curve validation is a separate ECDH
+`PUB_KEY_VERIFY` op, not part of the raw shared-secret compute. This path needs
+a PMD implementing ECDH (for example `crypto_qat`); the OpenSSL PMD does not
+implement ECDH, so it capability-skips these vectors. Curves the PMD does not
+support are capability-skipped rather than failed.
+
+ECDSA verification is supported for the `ecdsa_p1363_verify_schema_v1.json`
+schema. The group `publicKey.curve` selects the EC group and `publicKey.wx`,
+`publicKey.wy` map to the public point `q` in `rte_crypto_ec_xform`; the
+per-test `msg` is hashed with the group `sha` (using the leftmost `Ln` bits as
+`e`), and the fixed-width P1363 `sig` is split into `r` and `s`. `valid`
+signatures must verify; `invalid` ones must not. Non-canonical signature sizes
+(not `2 * ceil(bitlen(n)/8)`) are skipped, since the fixed-width representation
+cannot faithfully encode them. This path needs a PMD implementing ECDSA (for
+example `crypto_qat`, curves secp256r1/384r1/521r1); the OpenSSL PMD does not
+implement ECDSA, so it capability-skips these vectors.
+
+Digest computation for DSA and ECDSA uses the symmetric auth path. When the
+target device is asymmetric-only (for example the QAT asym device), the app
+automatically selects a separate symmetric-capable device (for example the QAT
+sym device) for hashing.
+
+The dispatcher also recognizes AES-CBC-PKCS5, AES-EAX, AES-FF1, AES-GCM-SIV,
+AES-KWP, AES-SIV-CMAC, AES-WRAP, AES-XTS, SM4-CCM, HMAC-SHA512/224,
+HMAC-SHA512/256, RSA, ECDSA (DER, WebCrypto, and Bitcoin schemas), DSA (DER
+`dsa_verify_schema_v1.json`), ECDH (DER, PEM, and WebCrypto schemas), and SEED
+cipher files. Their operation-specific adapters are not implemented yet, so
+they are reported as recognized-but-unsupported rather than silently folded
+into unrelated files. HMAC requires separate generation and verification
+operations; AES-XTS needs defined 64-bit data-unit-sequence to DPDK tweak
+conversion; the remaining AES modes need cipher, padding, key-wrap, SIV, or
+FPE adapters. DPDK exposes no SEED symmetric transform. RSA, the DER DSA
+schema, the DER/WebCrypto/Bitcoin ECDSA schemas, and the non-ecpoint ECDH
+schemas need per-schema padding, hash, key, and signature/point-encoding
+adapters.
+
+## Build
+
+Jansson is required for JSON parsing. Build this example from the DPDK source
+tree:
+
+```sh
+meson setup build -Dexamples=wycheproof_validation -Denable_drivers=crypto/openssl
+meson compile -C build
+```
+
+## Run
+
+Use the OpenSSL PMD for the first run. A file path processes that JSON file; a
+directory processes supported JSON files directly within that directory.
+Pass `--debug` to list every failed or skipped vector. Each record includes the
+algorithm, `tcId` and result where available, and its reason.
+
+```sh
+./build/examples/dpdk-wycheproof_validation --vdev crypto_openssl -- \
+ --vectors ../wycheproof/testvectors_v1/aes_gcm_test.json \
+ --cryptodev crypto_openssl --debug
+```
+
+ECDH needs a PMD that implements it (the OpenSSL PMD does not). Build with the
+QAT PMD (`-Denable_drivers=crypto/openssl,crypto/qat`) and run against a bound
+QAT asymmetric device:
+
+```sh
+./build/examples/dpdk-wycheproof_validation -a 0000:14:00.1 -- \
+ --vectors ../wycheproof/testvectors_v1/ecdh_secp256r1_ecpoint_test.json \
+ --cryptodev 0000:14:00.1_qat_asym
+```
+
+The program exits nonzero when a parsed, supported vector fails validation. It
+exits zero when all executed vectors pass, even when unsupported files or PMD
+parameter combinations are skipped; the summary exposes those counts.
diff --git a/examples/wycheproof_validation/main.c b/examples/wycheproof_validation/main.c
new file mode 100644
index 0000000000..06a208ab03
--- /dev/null
+++ b/examples/wycheproof_validation/main.c
@@ -0,0 +1,1882 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026
+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <jansson.h>
+
+#include <rte_cryptodev.h>
+#include <rte_eal.h>
+#include <rte_errno.h>
+#include <rte_mbuf.h>
+#include <rte_mempool.h>
+#include <rte_malloc.h>
+
+#define APP_NAME "wycheproof_validation"
+#define DEFAULT_MBUF_DATA_ROOM 16384
+#define IV_OFFSET (sizeof(struct rte_crypto_op) + sizeof(struct rte_crypto_sym_op))
+
+struct wycheproof_data {
+ uint8_t *key;
+ uint8_t *iv;
+ uint8_t *aad;
+ uint8_t *msg;
+ uint8_t *ct;
+ uint8_t *tag;
+ uint32_t key_len;
+ uint32_t iv_len;
+ uint32_t aad_len;
+ uint32_t msg_len;
+ uint32_t ct_len;
+ uint32_t tag_len;
+ json_int_t tc_id;
+ const char *result;
+};
+
+struct app_env {
+ uint8_t dev_id;
+ uint8_t hash_dev_id;
+ bool hash_dev_own;
+ uint32_t mbuf_data_room;
+ const char *vectors_path;
+ bool debug;
+ struct rte_mempool *mbuf_pool;
+ struct rte_mempool *session_pool;
+ struct rte_mempool *op_pool;
+ struct rte_mempool *asym_session_pool;
+ struct rte_mempool *asym_op_pool;
+};
+
+struct app_stats {
+ uint64_t passed;
+ uint64_t failed;
+ uint64_t skipped_capability;
+ uint64_t skipped_acceptable;
+ uint64_t skipped_unsupported;
+};
+
+static struct app_env env = {
+ .mbuf_data_room = DEFAULT_MBUF_DATA_ROOM,
+};
+
+static void
+usage(const char *program)
+{
+ printf("%s [EAL options] -- --vectors PATH [--cryptodev NAME | --cryptodev-id ID] [--debug]\n",
+ program);
+}
+
+static void
+debug_vector(const char *type, const char *algorithm,
+ const struct wycheproof_data *vector, const char *reason)
+{
+ if (env.debug)
+ printf("%s %s tcId=%" JSON_INTEGER_FORMAT " result=%s: %s\n", type,
+ algorithm, vector->tc_id, vector->result, reason);
+}
+
+static void
+debug_file(const char *type, const char *path, const char *reason)
+{
+ if (env.debug)
+ printf("%s %s: %s\n", type, path, reason);
+}
+
+static int
+parse_uint32(const char *text, uint32_t *value)
+{
+ char *end;
+ unsigned long parsed;
+
+ errno = 0;
+ parsed = strtoul(text, &end, 10);
+ if (errno != 0 || *text == '\0' || *end != '\0' || parsed > UINT32_MAX)
+ return -EINVAL;
+
+ *value = parsed;
+ return 0;
+}
+
+static int
+parse_args(int argc, char **argv)
+{
+ static const struct option options[] = {
+ { "vectors", required_argument, NULL, 'v' },
+ { "cryptodev", required_argument, NULL, 'c' },
+ { "cryptodev-id", required_argument, NULL, 'd' },
+ { "mbuf-dataroom", required_argument, NULL, 'm' },
+ { "debug", no_argument, NULL, 'D' },
+ { NULL, 0, NULL, 0 },
+ };
+ bool have_device = false;
+ int option;
+
+ if (rte_cryptodev_count() == 0)
+ return -ENODEV;
+ env.dev_id = 0;
+
+ while ((option = getopt_long(argc, argv, "v:c:d:m:D", options, NULL)) != -1) {
+ uint32_t value;
+ int dev_id;
+
+ switch (option) {
+ case 'v':
+ env.vectors_path = optarg;
+ break;
+ case 'c':
+ dev_id = rte_cryptodev_get_dev_id(optarg);
+ if (dev_id < 0)
+ return dev_id;
+ env.dev_id = dev_id;
+ have_device = true;
+ break;
+ case 'd':
+ if (parse_uint32(optarg, &value) != 0 ||
+ !rte_cryptodev_is_valid_dev(value))
+ return -EINVAL;
+ env.dev_id = value;
+ have_device = true;
+ break;
+ case 'm':
+ if (parse_uint32(optarg, &env.mbuf_data_room) != 0 ||
+ env.mbuf_data_room == 0)
+ return -EINVAL;
+ break;
+ case 'D':
+ env.debug = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ if (env.vectors_path == NULL)
+ return -EINVAL;
+ if (!have_device)
+ printf("Using cryptodev %u (%s)\n", env.dev_id,
+ rte_cryptodev_name_get(env.dev_id));
+
+ return 0;
+}
+
+static int
+app_init(void)
+{
+ struct rte_cryptodev_config config = { rte_socket_id(), 1, 0 };
+ struct rte_cryptodev_qp_conf queue_pair = { 128, NULL };
+ uint32_t session_size;
+ int ret;
+
+ ret = rte_cryptodev_configure(env.dev_id, &config);
+ if (ret < 0)
+ return ret;
+
+ env.mbuf_pool = rte_pktmbuf_pool_create("WYCHEPROOF_MBUF_POOL", 64, 0, 0,
+ sizeof(struct rte_mbuf) + RTE_PKTMBUF_HEADROOM + env.mbuf_data_room,
+ rte_socket_id());
+ if (env.mbuf_pool == NULL) {
+ ret = -rte_errno;
+ goto error;
+ }
+
+ session_size = rte_cryptodev_sym_get_private_session_size(env.dev_id);
+ {
+ /* The hashing device may differ from the target device, so size the
+ * session pool for the largest sym session across all devices.
+ */
+ uint8_t d;
+
+ for (d = 0; d < rte_cryptodev_count(); d++) {
+ uint32_t s = rte_cryptodev_sym_get_private_session_size(d);
+
+ if (s > session_size)
+ session_size = s;
+ }
+ }
+ env.session_pool = rte_cryptodev_sym_session_pool_create("WYCHEPROOF_SESSION_POOL",
+ 64, session_size, 0, 0, rte_socket_id());
+ if (env.session_pool == NULL) {
+ ret = -rte_errno;
+ goto error;
+ }
+
+ env.op_pool = rte_crypto_op_pool_create("WYCHEPROOF_OP_POOL",
+ RTE_CRYPTO_OP_TYPE_SYMMETRIC, 64, 0, IV_OFFSET + 64, rte_socket_id());
+ if (env.op_pool == NULL) {
+ ret = -rte_errno;
+ goto error;
+ }
+
+ /* Asymmetric pools are best-effort; DSA/ECDSA paths gate on the
+ * device asymmetric feature flag at runtime.
+ */
+ env.asym_session_pool = rte_cryptodev_asym_session_pool_create(
+ "WYCHEPROOF_ASYM_SESS", 16, 0, 0, rte_socket_id());
+ env.asym_op_pool = rte_crypto_op_pool_create("WYCHEPROOF_ASYM_OP",
+ RTE_CRYPTO_OP_TYPE_ASYMMETRIC, 64, 0, 0, rte_socket_id());
+
+ queue_pair.mp_session = env.session_pool;
+ ret = rte_cryptodev_queue_pair_setup(env.dev_id, 0, &queue_pair,
+ rte_socket_id());
+ if (ret < 0)
+ goto error;
+
+ ret = rte_cryptodev_start(env.dev_id);
+ if (ret < 0)
+ goto error;
+
+ /* Digest computation for DSA/ECDSA needs a symmetric-auth device. The
+ * target device may be asym-only (for example QAT), so pick a separate
+ * sym-capable device for hashing when needed.
+ */
+ env.hash_dev_id = env.dev_id;
+ env.hash_dev_own = false;
+ {
+ struct rte_cryptodev_sym_capability_idx hash_idx = {
+ .type = RTE_CRYPTO_SYM_XFORM_AUTH,
+ .algo.auth = RTE_CRYPTO_AUTH_SHA256,
+ };
+ uint8_t count = rte_cryptodev_count();
+ uint8_t d;
+
+ if (rte_cryptodev_sym_capability_get(env.dev_id, &hash_idx) == NULL) {
+ for (d = 0; d < count; d++) {
+ struct rte_cryptodev_qp_conf hash_qp = { 128, NULL };
+
+ if (d == env.dev_id ||
+ rte_cryptodev_sym_capability_get(d, &hash_idx) == NULL)
+ continue;
+ if (rte_cryptodev_configure(d, &config) < 0)
+ continue;
+ hash_qp.mp_session = env.session_pool;
+ if (rte_cryptodev_queue_pair_setup(d, 0, &hash_qp,
+ rte_socket_id()) < 0 ||
+ rte_cryptodev_start(d) < 0) {
+ rte_cryptodev_close(d);
+ continue;
+ }
+ env.hash_dev_id = d;
+ env.hash_dev_own = true;
+ break;
+ }
+ }
+ }
+
+ return 0;
+
+error:
+ rte_mempool_free(env.asym_op_pool);
+ rte_mempool_free(env.asym_session_pool);
+ rte_mempool_free(env.op_pool);
+ rte_mempool_free(env.session_pool);
+ rte_mempool_free(env.mbuf_pool);
+ env.asym_op_pool = NULL;
+ env.asym_session_pool = NULL;
+ env.op_pool = NULL;
+ env.session_pool = NULL;
+ env.mbuf_pool = NULL;
+ return ret;
+}
+
+static void
+app_uninit(void)
+{
+ if (env.hash_dev_own) {
+ rte_cryptodev_stop(env.hash_dev_id);
+ rte_cryptodev_close(env.hash_dev_id);
+ }
+ rte_cryptodev_stop(env.dev_id);
+ rte_cryptodev_close(env.dev_id);
+ rte_mempool_free(env.asym_op_pool);
+ rte_mempool_free(env.asym_session_pool);
+ rte_mempool_free(env.op_pool);
+ rte_mempool_free(env.session_pool);
+ rte_mempool_free(env.mbuf_pool);
+}
+
+static void
+free_vector(struct wycheproof_data *vector)
+{
+ rte_free(vector->key);
+ rte_free(vector->iv);
+ rte_free(vector->aad);
+ rte_free(vector->msg);
+ rte_free(vector->ct);
+ rte_free(vector->tag);
+ memset(vector, 0, sizeof(*vector));
+}
+
+static int
+hex_value(char character)
+{
+ if (character >= '0' && character <= '9')
+ return character - '0';
+ if (character >= 'a' && character <= 'f')
+ return character - 'a' + 10;
+ if (character >= 'A' && character <= 'F')
+ return character - 'A' + 10;
+ return -1;
+}
+
+static int
+decode_hex(json_t *value, uint8_t **buffer, uint32_t *length)
+{
+ const char *hex;
+ size_t hex_length;
+ uint8_t *decoded;
+ size_t index;
+
+ if (!json_is_string(value))
+ return -EINVAL;
+
+ hex = json_string_value(value);
+ hex_length = strlen(hex);
+ if ((hex_length & 1) != 0 || hex_length / 2 > UINT32_MAX)
+ return -EINVAL;
+
+ *length = hex_length / 2;
+ if (*length == 0)
+ return 0;
+
+ decoded = rte_malloc(NULL, *length, 0);
+ if (decoded == NULL)
+ return -ENOMEM;
+
+ for (index = 0; index < *length; index++) {
+ int high = hex_value(hex[index * 2]);
+ int low = hex_value(hex[index * 2 + 1]);
+
+ if (high < 0 || low < 0) {
+ rte_free(decoded);
+ return -EINVAL;
+ }
+ decoded[index] = (high << 4) | low;
+ }
+
+ *buffer = decoded;
+ return 0;
+}
+
+static int
+parse_vector(json_t *test, struct wycheproof_data *vector)
+{
+ json_t *tc_id;
+ int ret;
+
+ memset(vector, 0, sizeof(*vector));
+ tc_id = json_object_get(test, "tcId");
+ vector->result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector->result == NULL)
+ return -EINVAL;
+ vector->tc_id = json_integer_value(tc_id);
+
+ ret = decode_hex(json_object_get(test, "key"), &vector->key, &vector->key_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "iv"), &vector->iv, &vector->iv_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "aad"), &vector->aad, &vector->aad_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "msg"), &vector->msg, &vector->msg_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "ct"), &vector->ct, &vector->ct_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "tag"), &vector->tag, &vector->tag_len);
+ if (ret != 0)
+ goto error;
+
+ return 0;
+
+error:
+ free_vector(vector);
+ return ret;
+}
+
+static int
+parse_mac_vector(json_t *test, struct wycheproof_data *vector)
+{
+ json_t *tc_id;
+ int ret;
+
+ memset(vector, 0, sizeof(*vector));
+ tc_id = json_object_get(test, "tcId");
+ vector->result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector->result == NULL)
+ return -EINVAL;
+ vector->tc_id = json_integer_value(tc_id);
+
+ ret = decode_hex(json_object_get(test, "key"), &vector->key, &vector->key_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "msg"), &vector->msg, &vector->msg_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "tag"), &vector->tag, &vector->tag_len);
+ if (ret != 0)
+ goto error;
+
+ return 0;
+
+error:
+ free_vector(vector);
+ return ret;
+}
+
+static int
+parse_mac_with_iv_vector(json_t *test, struct wycheproof_data *vector)
+{
+ int ret;
+
+ ret = parse_mac_vector(test, vector);
+ if (ret != 0)
+ return ret;
+ ret = decode_hex(json_object_get(test, "iv"), &vector->iv, &vector->iv_len);
+ if (ret != 0)
+ free_vector(vector);
+ return ret;
+}
+
+static int
+check_aead_capability(const struct wycheproof_data *vector,
+ enum rte_crypto_aead_algorithm algorithm)
+{
+ struct rte_cryptodev_sym_capability_idx index = {
+ .type = RTE_CRYPTO_SYM_XFORM_AEAD,
+ .algo.aead = algorithm,
+ };
+ const struct rte_cryptodev_symmetric_capability *capability;
+
+ capability = rte_cryptodev_sym_capability_get(env.dev_id, &index);
+ if (capability == NULL)
+ return -ENOTSUP;
+
+ return rte_cryptodev_sym_capability_check_aead(capability, vector->key_len,
+ vector->tag_len, vector->aad_len, vector->iv_len);
+}
+
+static int
+run_aead(const struct wycheproof_data *vector, enum rte_crypto_aead_algorithm algorithm,
+ enum rte_crypto_aead_operation operation, uint8_t **output, uint8_t **digest,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_sym_xform xform = { 0 };
+ struct rte_cryptodev_sym_session *session = NULL;
+ struct rte_crypto_op *operation_op = NULL;
+ struct rte_mbuf *mbuf = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_crypto_sym_op *sym_op;
+ uint8_t *aad = NULL;
+ uint8_t *input;
+ uint32_t input_len;
+ int ret = -ENOMEM;
+
+ *output = NULL;
+ *digest = NULL;
+ xform.type = RTE_CRYPTO_SYM_XFORM_AEAD;
+ xform.aead.algo = algorithm;
+ xform.aead.op = operation;
+ xform.aead.key.data = vector->key;
+ xform.aead.key.length = vector->key_len;
+ xform.aead.iv.offset = IV_OFFSET;
+ xform.aead.iv.length = vector->iv_len;
+ xform.aead.aad_length = vector->aad_len;
+ xform.aead.digest_length = vector->tag_len;
+
+ session = rte_cryptodev_sym_session_create(env.dev_id, &xform, env.session_pool);
+ if (session == NULL)
+ goto out;
+
+ operation_op = rte_crypto_op_alloc(env.op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC);
+ if (operation_op == NULL)
+ goto out;
+
+ mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
+ if (mbuf == NULL)
+ goto out;
+ input = operation == RTE_CRYPTO_AEAD_OP_ENCRYPT ? vector->msg : vector->ct;
+ input_len = operation == RTE_CRYPTO_AEAD_OP_ENCRYPT ? vector->msg_len : vector->ct_len;
+ if (input_len > rte_pktmbuf_tailroom(mbuf)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ if (input_len != 0)
+ memcpy(rte_pktmbuf_append(mbuf, input_len), input, input_len);
+
+ *digest = rte_malloc(NULL, vector->tag_len, RTE_CACHE_LINE_SIZE);
+ if (*digest == NULL && vector->tag_len != 0)
+ goto out;
+ if (operation == RTE_CRYPTO_AEAD_OP_DECRYPT && vector->tag_len != 0)
+ memcpy(*digest, vector->tag, vector->tag_len);
+
+ sym_op = operation_op->sym;
+ sym_op->m_src = mbuf;
+ sym_op->aead.data.offset = 0;
+ sym_op->aead.data.length = input_len;
+ if (algorithm == RTE_CRYPTO_AEAD_AES_CCM) {
+ aad = rte_zmalloc(NULL, vector->aad_len + 18, 0);
+ if (aad == NULL && vector->aad_len != 0)
+ goto out;
+ if (vector->aad_len != 0)
+ memcpy(aad + 18, vector->aad, vector->aad_len);
+ sym_op->aead.aad.data = aad;
+ sym_op->aead.aad.phys_addr = rte_malloc_virt2iova(aad);
+ memcpy(rte_crypto_op_ctod_offset(operation_op, uint8_t *, IV_OFFSET) + 1,
+ vector->iv, vector->iv_len);
+ } else {
+ sym_op->aead.aad.data = vector->aad;
+ sym_op->aead.aad.phys_addr = rte_malloc_virt2iova(vector->aad);
+ memcpy(rte_crypto_op_ctod_offset(operation_op, uint8_t *, IV_OFFSET), vector->iv,
+ vector->iv_len);
+ }
+ sym_op->aead.digest.data = *digest;
+ sym_op->aead.digest.phys_addr = rte_malloc_virt2iova(*digest);
+ rte_crypto_op_attach_sym_session(operation_op, session);
+
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &operation_op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ while (rte_cryptodev_dequeue_burst(env.dev_id, 0, &completed, 1) == 0)
+ rte_pause();
+
+ *status = completed->status;
+ if (*status == RTE_CRYPTO_OP_STATUS_SUCCESS && input_len != 0) {
+ *output = rte_malloc(NULL, input_len, 0);
+ if (*output == NULL)
+ goto out;
+ memcpy(*output, rte_pktmbuf_mtod(mbuf, uint8_t *), input_len);
+ }
+ ret = 0;
+
+out:
+ rte_free(aad);
+ rte_pktmbuf_free(mbuf);
+ rte_crypto_op_free(operation_op);
+ rte_cryptodev_sym_session_free(env.dev_id, session);
+ if (ret != 0) {
+ rte_free(*output);
+ rte_free(*digest);
+ *output = NULL;
+ *digest = NULL;
+ }
+ return ret;
+}
+
+static int
+run_hmac(const struct wycheproof_data *vector, enum rte_crypto_auth_algorithm algorithm,
+ enum rte_crypto_auth_operation operation, uint8_t **digest,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_sym_xform xform = { 0 };
+ struct rte_cryptodev_sym_session *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_mbuf *mbuf = NULL;
+ const struct rte_cryptodev_symmetric_capability *capability;
+ struct rte_cryptodev_sym_capability_idx index = {
+ .type = RTE_CRYPTO_SYM_XFORM_AUTH,
+ .algo.auth = algorithm,
+ };
+ int ret = -ENOMEM;
+
+ *digest = NULL;
+ capability = rte_cryptodev_sym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_sym_capability_check_auth(capability,
+ vector->key_len, vector->tag_len, 0) != 0)
+ return -ENOTSUP;
+
+ xform.type = RTE_CRYPTO_SYM_XFORM_AUTH;
+ xform.auth.algo = algorithm;
+ xform.auth.op = operation;
+ xform.auth.key.data = vector->key;
+ xform.auth.key.length = vector->key_len;
+ xform.auth.digest_length = vector->tag_len;
+ session = rte_cryptodev_sym_session_create(env.dev_id, &xform, env.session_pool);
+ if (session == NULL)
+ goto out;
+
+ op = rte_crypto_op_alloc(env.op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC);
+ if (op == NULL)
+ goto out;
+ mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
+ if (mbuf == NULL)
+ goto out;
+ if (vector->msg_len > rte_pktmbuf_tailroom(mbuf)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ if (vector->msg_len != 0)
+ memcpy(rte_pktmbuf_append(mbuf, vector->msg_len), vector->msg, vector->msg_len);
+
+ *digest = rte_malloc(NULL, vector->tag_len, RTE_CACHE_LINE_SIZE);
+ if (*digest == NULL && vector->tag_len != 0)
+ goto out;
+ if (operation == RTE_CRYPTO_AUTH_OP_VERIFY && vector->tag_len != 0)
+ memcpy(*digest, vector->tag, vector->tag_len);
+
+ op->sym->m_src = mbuf;
+ op->sym->auth.data.offset = 0;
+ op->sym->auth.data.length = vector->msg_len;
+ op->sym->auth.digest.data = *digest;
+ op->sym->auth.digest.phys_addr = rte_malloc_virt2iova(*digest);
+ rte_crypto_op_attach_sym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ while (rte_cryptodev_dequeue_burst(env.dev_id, 0, &completed, 1) == 0)
+ rte_pause();
+ *status = completed->status;
+ ret = 0;
+
+out:
+ rte_pktmbuf_free(mbuf);
+ rte_crypto_op_free(op);
+ rte_cryptodev_sym_session_free(env.dev_id, session);
+ if (ret != 0) {
+ rte_free(*digest);
+ *digest = NULL;
+ }
+ return ret;
+}
+
+static int
+run_gmac(const struct wycheproof_data *vector, enum rte_crypto_auth_operation operation,
+ uint8_t **digest, enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_sym_xform xform = { 0 };
+ struct rte_cryptodev_sym_session *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_mbuf *mbuf = NULL;
+ const struct rte_cryptodev_symmetric_capability *capability;
+ struct rte_cryptodev_sym_capability_idx index = {
+ .type = RTE_CRYPTO_SYM_XFORM_AUTH,
+ .algo.auth = RTE_CRYPTO_AUTH_AES_GMAC,
+ };
+ int ret = -ENOMEM;
+
+ *digest = NULL;
+ capability = rte_cryptodev_sym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_sym_capability_check_auth(capability,
+ vector->key_len, vector->tag_len, vector->iv_len) != 0)
+ return -ENOTSUP;
+
+ xform.type = RTE_CRYPTO_SYM_XFORM_AUTH;
+ xform.auth.algo = RTE_CRYPTO_AUTH_AES_GMAC;
+ xform.auth.op = operation;
+ xform.auth.key.data = vector->key;
+ xform.auth.key.length = vector->key_len;
+ xform.auth.iv.offset = IV_OFFSET;
+ xform.auth.iv.length = vector->iv_len;
+ xform.auth.digest_length = vector->tag_len;
+ session = rte_cryptodev_sym_session_create(env.dev_id, &xform, env.session_pool);
+ if (session == NULL)
+ goto out;
+
+ op = rte_crypto_op_alloc(env.op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC);
+ if (op == NULL)
+ goto out;
+ mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
+ if (mbuf == NULL)
+ goto out;
+ if (vector->msg_len > rte_pktmbuf_tailroom(mbuf)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ if (vector->msg_len != 0)
+ memcpy(rte_pktmbuf_append(mbuf, vector->msg_len), vector->msg, vector->msg_len);
+
+ *digest = rte_malloc(NULL, vector->tag_len, RTE_CACHE_LINE_SIZE);
+ if (*digest == NULL && vector->tag_len != 0)
+ goto out;
+ if (operation == RTE_CRYPTO_AUTH_OP_VERIFY && vector->tag_len != 0)
+ memcpy(*digest, vector->tag, vector->tag_len);
+
+ op->sym->m_src = mbuf;
+ op->sym->auth.data.offset = 0;
+ op->sym->auth.data.length = vector->msg_len;
+ op->sym->auth.digest.data = *digest;
+ op->sym->auth.digest.phys_addr = rte_malloc_virt2iova(*digest);
+ memcpy(rte_crypto_op_ctod_offset(op, uint8_t *, IV_OFFSET), vector->iv, vector->iv_len);
+ rte_crypto_op_attach_sym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ while (rte_cryptodev_dequeue_burst(env.dev_id, 0, &completed, 1) == 0)
+ rte_pause();
+ *status = completed->status;
+ ret = 0;
+
+out:
+ rte_pktmbuf_free(mbuf);
+ rte_crypto_op_free(op);
+ rte_cryptodev_sym_session_free(env.dev_id, session);
+ if (ret != 0) {
+ rte_free(*digest);
+ *digest = NULL;
+ }
+ return ret;
+}
+
+static void
+validate_aead_vector(const struct wycheproof_data *vector,
+ enum rte_crypto_aead_algorithm algorithm, const char *name,
+ struct app_stats *stats)
+{
+ uint8_t *output = NULL;
+ uint8_t *digest = NULL;
+ enum rte_crypto_op_status status;
+ int ret;
+
+ if (strcmp(vector->result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, vector, "acceptable result policy");
+ return;
+ }
+ if (strcmp(vector->result, "valid") != 0 && strcmp(vector->result, "invalid") != 0) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, vector, "unknown result");
+ return;
+ }
+ if (vector->msg_len != vector->ct_len ||
+ vector->msg_len > env.mbuf_data_room - RTE_PKTMBUF_HEADROOM) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, vector, "unsupported message length");
+ return;
+ }
+ if (check_aead_capability(vector, algorithm) != 0) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, vector, "PMD capability");
+ return;
+ }
+
+ if (strcmp(vector->result, "valid") == 0) {
+ ret = run_aead(vector, algorithm, RTE_CRYPTO_AEAD_OP_ENCRYPT, &output, &digest,
+ &status);
+ if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS ||
+ memcmp(output, vector->ct, vector->ct_len) != 0 ||
+ memcmp(digest, vector->tag, vector->tag_len) != 0)
+ goto failed;
+ rte_free(output);
+ rte_free(digest);
+ output = NULL;
+ digest = NULL;
+ ret = run_aead(vector, algorithm, RTE_CRYPTO_AEAD_OP_DECRYPT, &output, &digest,
+ &status);
+ if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS ||
+ memcmp(output, vector->msg, vector->msg_len) != 0)
+ goto failed;
+ } else {
+ ret = run_aead(vector, algorithm, RTE_CRYPTO_AEAD_OP_DECRYPT, &output, &digest,
+ &status);
+ if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_AUTH_FAILED)
+ goto failed;
+ }
+
+ stats->passed++;
+ rte_free(output);
+ rte_free(digest);
+ return;
+
+failed:
+ if (env.debug)
+ printf("FAIL %s tcId=%" JSON_INTEGER_FORMAT " result=%s status=%d ret=%d\n",
+ name, vector->tc_id, vector->result, status, ret);
+ stats->failed++;
+ rte_free(output);
+ rte_free(digest);
+}
+
+static int
+process_aead(json_t *root, enum rte_crypto_aead_algorithm algorithm,
+ const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+
+ json_array_foreach(groups, group_index, group) {
+ json_t *tests = json_object_get(group, "tests");
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_array(tests))
+ return -EINVAL;
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ int ret = parse_vector(test, &vector);
+
+ if (ret != 0) {
+ if (env.debug)
+ printf("SKIP %s group=%zu test=%zu: malformed vector\n", name,
+ group_index, test_index);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ validate_aead_vector(&vector, algorithm, name, stats);
+ free_vector(&vector);
+ }
+ }
+
+ return 0;
+}
+
+static int
+process_hmac(json_t *root, enum rte_crypto_auth_algorithm algorithm,
+ const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *tests = json_object_get(group, "tests");
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_array(tests))
+ return -EINVAL;
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *digest = NULL;
+ enum rte_crypto_op_status status;
+ int ret;
+
+ ret = parse_mac_vector(test, &vector);
+ if (ret != 0) {
+ if (env.debug)
+ printf("SKIP %s group=%zu test=%zu: malformed vector\n", name,
+ group_index, test_index);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, &vector, "acceptable result policy");
+ }
+ else if (strcmp(vector.result, "valid") == 0) {
+ ret = run_hmac(&vector, algorithm, RTE_CRYPTO_AUTH_OP_GENERATE,
+ &digest, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ }
+ else if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS ||
+ memcmp(digest, vector.tag, vector.tag_len) != 0) {
+ debug_vector("FAIL", name, &vector, "generated tag mismatch");
+ stats->failed++;
+ } else
+ stats->passed++;
+ } else if (strcmp(vector.result, "invalid") == 0) {
+ ret = run_hmac(&vector, algorithm, RTE_CRYPTO_AUTH_OP_VERIFY,
+ &digest, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ }
+ else if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_AUTH_FAILED) {
+ debug_vector("FAIL", name, &vector, "authentication unexpectedly succeeded");
+ stats->failed++;
+ } else
+ stats->passed++;
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "unknown result");
+ }
+ rte_free(digest);
+ free_vector(&vector);
+ }
+ }
+ return 0;
+}
+
+static int
+process_gmac(json_t *root, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *tests = json_object_get(group, "tests");
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_array(tests))
+ return -EINVAL;
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *digest = NULL;
+ enum rte_crypto_op_status status;
+ int ret;
+
+ ret = parse_mac_with_iv_vector(test, &vector);
+ if (ret != 0) {
+ if (env.debug)
+ printf("SKIP AES-GMAC group=%zu test=%zu: malformed vector\n",
+ group_index, test_index);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "valid") == 0)
+ ret = run_gmac(&vector, RTE_CRYPTO_AUTH_OP_GENERATE, &digest, &status);
+ else if (strcmp(vector.result, "invalid") == 0)
+ ret = run_gmac(&vector, RTE_CRYPTO_AUTH_OP_VERIFY, &digest, &status);
+ else {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", "AES-GMAC", &vector, "acceptable result policy");
+ ret = 0;
+ }
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", "AES-GMAC", &vector, "PMD capability");
+ }
+ else if ((strcmp(vector.result, "valid") == 0 &&
+ (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS ||
+ memcmp(digest, vector.tag, vector.tag_len) != 0)) ||
+ (strcmp(vector.result, "invalid") == 0 &&
+ (ret != 0 || status != RTE_CRYPTO_OP_STATUS_AUTH_FAILED))) {
+ debug_vector("FAIL", "AES-GMAC", &vector, "unexpected operation result");
+ stats->failed++;
+ } else if (strcmp(vector.result, "valid") == 0 ||
+ strcmp(vector.result, "invalid") == 0)
+ stats->passed++;
+ rte_free(digest);
+ free_vector(&vector);
+ }
+ }
+ return 0;
+}
+
+struct dsa_group {
+ uint8_t *p;
+ uint8_t *q;
+ uint8_t *g;
+ uint8_t *y;
+ uint32_t p_len;
+ uint32_t q_len;
+ uint32_t g_len;
+ uint32_t y_len;
+ enum rte_crypto_auth_algorithm hash;
+ uint32_t digest_len;
+};
+
+static int
+map_sha(const char *name, enum rte_crypto_auth_algorithm *algo, uint32_t *digest_len)
+{
+ if (strcmp(name, "SHA-1") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA1;
+ *digest_len = 20;
+ } else if (strcmp(name, "SHA-224") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA224;
+ *digest_len = 28;
+ } else if (strcmp(name, "SHA-256") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA256;
+ *digest_len = 32;
+ } else if (strcmp(name, "SHA-384") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA384;
+ *digest_len = 48;
+ } else if (strcmp(name, "SHA-512") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA512;
+ *digest_len = 64;
+ } else {
+ return -ENOTSUP;
+ }
+ return 0;
+}
+
+/* Compute a plain (keyless) message digest through the cryptodev auth path. */
+static int
+compute_hash(const uint8_t *msg, uint32_t msg_len, enum rte_crypto_auth_algorithm algo,
+ uint8_t *digest, uint32_t digest_len)
+{
+ struct rte_crypto_sym_xform xform = { 0 };
+ struct rte_cryptodev_sym_session *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_mbuf *mbuf = NULL;
+ const struct rte_cryptodev_symmetric_capability *capability;
+ struct rte_cryptodev_sym_capability_idx index = {
+ .type = RTE_CRYPTO_SYM_XFORM_AUTH,
+ .algo.auth = algo,
+ };
+ uint8_t *op_digest = NULL;
+ int ret = -ENOMEM;
+
+ capability = rte_cryptodev_sym_capability_get(env.hash_dev_id, &index);
+ if (capability == NULL || rte_cryptodev_sym_capability_check_auth(capability,
+ 0, digest_len, 0) != 0)
+ return -ENOTSUP;
+
+ xform.type = RTE_CRYPTO_SYM_XFORM_AUTH;
+ xform.auth.algo = algo;
+ xform.auth.op = RTE_CRYPTO_AUTH_OP_GENERATE;
+ xform.auth.digest_length = digest_len;
+ session = rte_cryptodev_sym_session_create(env.hash_dev_id, &xform, env.session_pool);
+ if (session == NULL)
+ goto out;
+
+ op = rte_crypto_op_alloc(env.op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC);
+ if (op == NULL)
+ goto out;
+ mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
+ if (mbuf == NULL)
+ goto out;
+ if (msg_len > rte_pktmbuf_tailroom(mbuf)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ if (msg_len != 0)
+ memcpy(rte_pktmbuf_append(mbuf, msg_len), msg, msg_len);
+
+ op_digest = rte_malloc(NULL, digest_len, RTE_CACHE_LINE_SIZE);
+ if (op_digest == NULL)
+ goto out;
+ op->sym->m_src = mbuf;
+ op->sym->auth.data.offset = 0;
+ op->sym->auth.data.length = msg_len;
+ op->sym->auth.digest.data = op_digest;
+ op->sym->auth.digest.phys_addr = rte_malloc_virt2iova(op_digest);
+ rte_crypto_op_attach_sym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.hash_dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ while (rte_cryptodev_dequeue_burst(env.hash_dev_id, 0, &completed, 1) == 0)
+ rte_pause();
+ if (completed->status != RTE_CRYPTO_OP_STATUS_SUCCESS) {
+ ret = -EIO;
+ goto out;
+ }
+ memcpy(digest, op_digest, digest_len);
+ ret = 0;
+
+out:
+ rte_free(op_digest);
+ rte_pktmbuf_free(mbuf);
+ rte_crypto_op_free(op);
+ rte_cryptodev_sym_session_free(env.hash_dev_id, session);
+ return ret;
+}
+
+static int
+run_dsa_verify(const struct dsa_group *group, const uint8_t *msg, uint32_t msg_len,
+ const uint8_t *r, uint32_t r_len, const uint8_t *s, uint32_t s_len,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_asym_xform xform = { 0 };
+ void *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_cryptodev_info dev_info;
+ struct rte_cryptodev_asym_capability_idx index = {
+ .type = RTE_CRYPTO_ASYM_XFORM_DSA,
+ };
+ const struct rte_cryptodev_asymmetric_xform_capability *capability;
+ uint8_t digest[64];
+ int ret;
+
+ if (env.asym_session_pool == NULL || env.asym_op_pool == NULL)
+ return -ENOTSUP;
+ rte_cryptodev_info_get(env.dev_id, &dev_info);
+ if ((dev_info.feature_flags & RTE_CRYPTODEV_FF_ASYMMETRIC_CRYPTO) == 0)
+ return -ENOTSUP;
+ capability = rte_cryptodev_asym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_asym_xform_capability_check_optype(capability,
+ RTE_CRYPTO_ASYM_OP_VERIFY) == 0)
+ return -ENOTSUP;
+
+ ret = compute_hash(msg, msg_len, group->hash, digest, group->digest_len);
+ if (ret != 0)
+ return ret;
+
+ xform.xform_type = RTE_CRYPTO_ASYM_XFORM_DSA;
+ xform.next = NULL;
+ xform.dsa.p.data = group->p;
+ xform.dsa.p.length = group->p_len;
+ xform.dsa.q.data = group->q;
+ xform.dsa.q.length = group->q_len;
+ xform.dsa.g.data = group->g;
+ xform.dsa.g.length = group->g_len;
+
+ ret = -ENOMEM;
+ if (rte_cryptodev_asym_session_create(env.dev_id, &xform, env.asym_session_pool,
+ &session) < 0 || session == NULL)
+ goto out;
+ op = rte_crypto_op_alloc(env.asym_op_pool, RTE_CRYPTO_OP_TYPE_ASYMMETRIC);
+ if (op == NULL)
+ goto out;
+ op->asym->dsa.op_type = RTE_CRYPTO_ASYM_OP_VERIFY;
+ /* DSA verify operates on the message digest. */
+ op->asym->dsa.message.data = digest;
+ op->asym->dsa.message.length = group->digest_len;
+ op->asym->dsa.r.data = (uint8_t *)(uintptr_t)r;
+ op->asym->dsa.r.length = r_len;
+ op->asym->dsa.s.data = (uint8_t *)(uintptr_t)s;
+ op->asym->dsa.s.length = s_len;
+ op->asym->dsa.y.data = group->y;
+ op->asym->dsa.y.length = group->y_len;
+ rte_crypto_op_attach_asym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ while (rte_cryptodev_dequeue_burst(env.dev_id, 0, &completed, 1) == 0)
+ rte_pause();
+ *status = completed->status;
+ ret = 0;
+
+out:
+ rte_crypto_op_free(op);
+ if (session != NULL)
+ rte_cryptodev_asym_session_free(env.dev_id, session);
+ return ret;
+}
+
+static int
+process_dsa_p1363(json_t *root, const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *pub = json_object_get(group, "publicKey");
+ json_t *tests = json_object_get(group, "tests");
+ struct dsa_group dsa = { 0 };
+ const char *sha;
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_object(pub) || !json_is_array(tests))
+ return -EINVAL;
+ sha = json_string_value(json_object_get(group, "sha"));
+ if (sha == NULL || map_sha(sha, &dsa.hash, &dsa.digest_len) != 0) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (decode_hex(json_object_get(pub, "p"), &dsa.p, &dsa.p_len) != 0 ||
+ decode_hex(json_object_get(pub, "q"), &dsa.q, &dsa.q_len) != 0 ||
+ decode_hex(json_object_get(pub, "g"), &dsa.g, &dsa.g_len) != 0 ||
+ decode_hex(json_object_get(pub, "y"), &dsa.y, &dsa.y_len) != 0) {
+ rte_free(dsa.p);
+ rte_free(dsa.q);
+ rte_free(dsa.g);
+ rte_free(dsa.y);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *sig = NULL;
+ uint32_t sig_len = 0;
+ enum rte_crypto_op_status status;
+ json_t *tc_id;
+ int ret;
+
+ memset(&vector, 0, sizeof(vector));
+ tc_id = json_object_get(test, "tcId");
+ vector.result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector.result == NULL) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ vector.tc_id = json_integer_value(tc_id);
+ if (decode_hex(json_object_get(test, "msg"), &vector.msg,
+ &vector.msg_len) != 0 ||
+ decode_hex(json_object_get(test, "sig"), &sig, &sig_len) != 0) {
+ rte_free(vector.msg);
+ rte_free(sig);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, &vector, "acceptable result policy");
+ rte_free(vector.msg);
+ rte_free(sig);
+ continue;
+ }
+ if ((sig_len & 1) != 0 || sig_len == 0) {
+ debug_vector("SKIP", name, &vector, "malformed P1363 signature");
+ stats->skipped_unsupported++;
+ rte_free(vector.msg);
+ rte_free(sig);
+ continue;
+ }
+ ret = run_dsa_verify(&dsa, vector.msg, vector.msg_len, sig, sig_len / 2,
+ sig + sig_len / 2, sig_len / 2, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ } else if ((strcmp(vector.result, "valid") == 0 &&
+ (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS)) ||
+ (strcmp(vector.result, "invalid") == 0 &&
+ (ret != 0 || status == RTE_CRYPTO_OP_STATUS_SUCCESS))) {
+ debug_vector("FAIL", name, &vector, "unexpected verification result");
+ stats->failed++;
+ } else if (strcmp(vector.result, "valid") == 0 ||
+ strcmp(vector.result, "invalid") == 0) {
+ stats->passed++;
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "unknown result");
+ }
+ rte_free(vector.msg);
+ rte_free(sig);
+ }
+ rte_free(dsa.p);
+ rte_free(dsa.q);
+ rte_free(dsa.g);
+ rte_free(dsa.y);
+ }
+ return 0;
+}
+
+static int
+map_curve(const char *name, enum rte_crypto_curve_id *curve, uint32_t *bytesize)
+{
+ if (strcmp(name, "secp224r1") == 0) {
+ *curve = RTE_CRYPTO_EC_GROUP_SECP224R1;
+ *bytesize = 28;
+ } else if (strcmp(name, "secp256r1") == 0) {
+ *curve = RTE_CRYPTO_EC_GROUP_SECP256R1;
+ *bytesize = 32;
+ } else if (strcmp(name, "secp384r1") == 0) {
+ *curve = RTE_CRYPTO_EC_GROUP_SECP384R1;
+ *bytesize = 48;
+ } else if (strcmp(name, "secp521r1") == 0) {
+ *curve = RTE_CRYPTO_EC_GROUP_SECP521R1;
+ *bytesize = 66;
+ } else {
+ return -ENOTSUP;
+ }
+ return 0;
+}
+
+/* Left-align an unsigned big-endian value into a fixed-width field. */
+static bool
+value_equals_padded(const uint8_t *a, uint32_t a_len, const uint8_t *b, uint32_t b_len)
+{
+ while (a_len > 0 && a[0] == 0) {
+ a++;
+ a_len--;
+ }
+ while (b_len > 0 && b[0] == 0) {
+ b++;
+ b_len--;
+ }
+ return a_len == b_len && memcmp(a, b, a_len) == 0;
+}
+
+static int
+run_ecdh_ecpoint(enum rte_crypto_curve_id curve, uint32_t bytesize,
+ const uint8_t *priv, uint32_t priv_len, const uint8_t *pub_x,
+ const uint8_t *pub_y, uint8_t **shared_x, uint32_t *shared_len,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_asym_xform xform = { 0 };
+ void *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_cryptodev_info dev_info;
+ struct rte_cryptodev_asym_capability_idx index = {
+ .type = RTE_CRYPTO_ASYM_XFORM_ECDH,
+ };
+ const struct rte_cryptodev_asymmetric_xform_capability *capability;
+ uint8_t *out_x = NULL;
+ uint8_t *out_y = NULL;
+ int ret;
+
+ *shared_x = NULL;
+ if (env.asym_session_pool == NULL || env.asym_op_pool == NULL)
+ return -ENOTSUP;
+ rte_cryptodev_info_get(env.dev_id, &dev_info);
+ if ((dev_info.feature_flags & RTE_CRYPTODEV_FF_ASYMMETRIC_CRYPTO) == 0)
+ return -ENOTSUP;
+ capability = rte_cryptodev_asym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_asym_xform_capability_check_optype(capability,
+ (enum rte_crypto_asym_op_type)RTE_CRYPTO_ASYM_KE_SHARED_SECRET_COMPUTE) == 0)
+ return -ENOTSUP;
+
+ xform.xform_type = RTE_CRYPTO_ASYM_XFORM_ECDH;
+ xform.next = NULL;
+ xform.ec.curve_id = curve;
+ xform.ec.pkey.data = (uint8_t *)(uintptr_t)priv;
+ xform.ec.pkey.length = priv_len;
+ xform.ec.q.x.data = (uint8_t *)(uintptr_t)pub_x;
+ xform.ec.q.x.length = bytesize;
+ xform.ec.q.y.data = (uint8_t *)(uintptr_t)pub_y;
+ xform.ec.q.y.length = bytesize;
+
+ ret = -ENOMEM;
+ out_x = rte_zmalloc(NULL, bytesize, 0);
+ out_y = rte_zmalloc(NULL, bytesize, 0);
+ if (out_x == NULL || out_y == NULL)
+ goto out;
+ /* A session-create failure means the PMD cannot handle this curve. */
+ if (rte_cryptodev_asym_session_create(env.dev_id, &xform, env.asym_session_pool,
+ &session) < 0 || session == NULL) {
+ ret = -ENOTSUP;
+ goto out;
+ }
+ op = rte_crypto_op_alloc(env.asym_op_pool, RTE_CRYPTO_OP_TYPE_ASYMMETRIC);
+ if (op == NULL)
+ goto out;
+ op->asym->ecdh.ke_type = RTE_CRYPTO_ASYM_KE_SHARED_SECRET_COMPUTE;
+ op->asym->ecdh.priv_key.data = (uint8_t *)(uintptr_t)priv;
+ op->asym->ecdh.priv_key.length = priv_len;
+ op->asym->ecdh.pub_key.x.data = (uint8_t *)(uintptr_t)pub_x;
+ op->asym->ecdh.pub_key.x.length = bytesize;
+ op->asym->ecdh.pub_key.y.data = (uint8_t *)(uintptr_t)pub_y;
+ op->asym->ecdh.pub_key.y.length = bytesize;
+ op->asym->ecdh.shared_secret.x.data = out_x;
+ op->asym->ecdh.shared_secret.x.length = bytesize;
+ op->asym->ecdh.shared_secret.y.data = out_y;
+ op->asym->ecdh.shared_secret.y.length = bytesize;
+ rte_crypto_op_attach_asym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ while (rte_cryptodev_dequeue_burst(env.dev_id, 0, &completed, 1) == 0)
+ rte_pause();
+ *status = completed->status;
+ if (completed->status == RTE_CRYPTO_OP_STATUS_SUCCESS) {
+ *shared_x = out_x;
+ *shared_len = completed->asym->ecdh.shared_secret.x.length;
+ out_x = NULL;
+ }
+ ret = 0;
+
+out:
+ rte_free(out_x);
+ rte_free(out_y);
+ rte_crypto_op_free(op);
+ if (session != NULL)
+ rte_cryptodev_asym_session_free(env.dev_id, session);
+ return ret;
+}
+
+static int
+process_ecdh_ecpoint(json_t *root, const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *tests = json_object_get(group, "tests");
+ enum rte_crypto_curve_id curve;
+ uint32_t bytesize;
+ const char *curve_name;
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_array(tests))
+ return -EINVAL;
+ curve_name = json_string_value(json_object_get(group, "curve"));
+ if (curve_name == NULL || map_curve(curve_name, &curve, &bytesize) != 0) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *pub = NULL;
+ uint8_t *priv = NULL;
+ uint8_t *shared = NULL;
+ uint8_t *result_x = NULL;
+ uint32_t pub_len = 0, priv_len = 0, shared_len = 0, result_len = 0;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ bool matched;
+ json_t *tc_id;
+ int ret;
+
+ memset(&vector, 0, sizeof(vector));
+ tc_id = json_object_get(test, "tcId");
+ vector.result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector.result == NULL) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ vector.tc_id = json_integer_value(tc_id);
+ if (decode_hex(json_object_get(test, "public"), &pub, &pub_len) != 0 ||
+ decode_hex(json_object_get(test, "private"), &priv,
+ &priv_len) != 0 ||
+ decode_hex(json_object_get(test, "shared"), &shared,
+ &shared_len) != 0) {
+ rte_free(pub);
+ rte_free(priv);
+ rte_free(shared);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, &vector, "acceptable result policy");
+ goto next;
+ }
+ /* Only uncompressed points (0x04 || x || y) map to the DPDK EC point. */
+ if (pub_len != 1 + 2 * bytesize || pub[0] != 0x04) {
+ debug_vector("SKIP", name, &vector, "non-uncompressed public point");
+ stats->skipped_unsupported++;
+ goto next;
+ }
+ /* Invalid-curve rejection cases carry no expected shared secret;
+ * on-curve validation is a separate ECDH PUB_KEY_VERIFY op, not
+ * part of the raw shared-secret compute primitive.
+ */
+ if (shared_len == 0) {
+ debug_vector("SKIP", name, &vector,
+ "no expected shared secret (point validation out of scope)");
+ stats->skipped_unsupported++;
+ goto next;
+ }
+ ret = run_ecdh_ecpoint(curve, bytesize, priv, priv_len, pub + 1,
+ pub + 1 + bytesize, &result_x, &result_len, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ goto next;
+ }
+ matched = ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS &&
+ value_equals_padded(result_x, result_len, shared, shared_len);
+ if (strcmp(vector.result, "valid") == 0) {
+ if (ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS && matched) {
+ stats->passed++;
+ } else if (ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS) {
+ debug_vector("FAIL", name, &vector, "wrong shared secret");
+ stats->failed++;
+ } else {
+ /* PMD returned an error for a valid vector. */
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD could not compute");
+ }
+ } else if (strcmp(vector.result, "invalid") == 0) {
+ if (matched) {
+ debug_vector("FAIL", name, &vector, "produced forbidden secret");
+ stats->failed++;
+ } else {
+ stats->passed++;
+ }
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "unknown result");
+ }
+next:
+ rte_free(result_x);
+ rte_free(pub);
+ rte_free(priv);
+ rte_free(shared);
+ }
+ }
+ return 0;
+}
+
+struct ecdsa_group {
+ uint8_t *wx;
+ uint8_t *wy;
+ uint32_t wx_len;
+ uint32_t wy_len;
+ enum rte_crypto_curve_id curve;
+ uint32_t bytesize;
+ enum rte_crypto_auth_algorithm hash;
+ uint32_t digest_len;
+};
+
+static int
+run_ecdsa_verify(const struct ecdsa_group *group, const uint8_t *msg, uint32_t msg_len,
+ const uint8_t *r, uint32_t r_len, const uint8_t *s, uint32_t s_len,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_asym_xform xform = { 0 };
+ void *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_cryptodev_info dev_info;
+ struct rte_cryptodev_asym_capability_idx index = {
+ .type = RTE_CRYPTO_ASYM_XFORM_ECDSA,
+ };
+ const struct rte_cryptodev_asymmetric_xform_capability *capability;
+ uint8_t digest[64];
+ uint32_t e_len;
+ int ret;
+
+ if (env.asym_session_pool == NULL || env.asym_op_pool == NULL)
+ return -ENOTSUP;
+ rte_cryptodev_info_get(env.dev_id, &dev_info);
+ if ((dev_info.feature_flags & RTE_CRYPTODEV_FF_ASYMMETRIC_CRYPTO) == 0)
+ return -ENOTSUP;
+ capability = rte_cryptodev_asym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_asym_xform_capability_check_optype(capability,
+ RTE_CRYPTO_ASYM_OP_VERIFY) == 0)
+ return -ENOTSUP;
+
+ ret = compute_hash(msg, msg_len, group->hash, digest, group->digest_len);
+ if (ret != 0)
+ return ret;
+ /* ECDSA uses e = leftmost Ln bits of the hash (byte-aligned truncation). */
+ e_len = group->digest_len > group->bytesize ? group->bytesize : group->digest_len;
+
+ xform.xform_type = RTE_CRYPTO_ASYM_XFORM_ECDSA;
+ xform.next = NULL;
+ xform.ec.curve_id = group->curve;
+ xform.ec.q.x.data = group->wx;
+ xform.ec.q.x.length = group->wx_len;
+ xform.ec.q.y.data = group->wy;
+ xform.ec.q.y.length = group->wy_len;
+
+ ret = -ENOMEM;
+ /* A session-create failure means the PMD cannot handle this curve. */
+ if (rte_cryptodev_asym_session_create(env.dev_id, &xform, env.asym_session_pool,
+ &session) < 0 || session == NULL) {
+ ret = -ENOTSUP;
+ goto out;
+ }
+ op = rte_crypto_op_alloc(env.asym_op_pool, RTE_CRYPTO_OP_TYPE_ASYMMETRIC);
+ if (op == NULL)
+ goto out;
+ op->asym->ecdsa.op_type = RTE_CRYPTO_ASYM_OP_VERIFY;
+ op->asym->ecdsa.message.data = digest;
+ op->asym->ecdsa.message.length = e_len;
+ op->asym->ecdsa.r.data = (uint8_t *)(uintptr_t)r;
+ op->asym->ecdsa.r.length = r_len;
+ op->asym->ecdsa.s.data = (uint8_t *)(uintptr_t)s;
+ op->asym->ecdsa.s.length = s_len;
+ rte_crypto_op_attach_asym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ while (rte_cryptodev_dequeue_burst(env.dev_id, 0, &completed, 1) == 0)
+ rte_pause();
+ *status = completed->status;
+ ret = 0;
+
+out:
+ rte_crypto_op_free(op);
+ if (session != NULL)
+ rte_cryptodev_asym_session_free(env.dev_id, session);
+ return ret;
+}
+
+static int
+process_ecdsa_p1363(json_t *root, const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *pub = json_object_get(group, "publicKey");
+ json_t *tests = json_object_get(group, "tests");
+ struct ecdsa_group ec = { 0 };
+ const char *sha;
+ const char *curve_name;
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_object(pub) || !json_is_array(tests))
+ return -EINVAL;
+ sha = json_string_value(json_object_get(group, "sha"));
+ curve_name = json_string_value(json_object_get(pub, "curve"));
+ if (sha == NULL || curve_name == NULL ||
+ map_sha(sha, &ec.hash, &ec.digest_len) != 0 ||
+ map_curve(curve_name, &ec.curve, &ec.bytesize) != 0) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (decode_hex(json_object_get(pub, "wx"), &ec.wx, &ec.wx_len) != 0 ||
+ decode_hex(json_object_get(pub, "wy"), &ec.wy, &ec.wy_len) != 0) {
+ rte_free(ec.wx);
+ rte_free(ec.wy);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *sig = NULL;
+ uint32_t sig_len = 0;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ json_t *tc_id;
+ int ret;
+
+ memset(&vector, 0, sizeof(vector));
+ tc_id = json_object_get(test, "tcId");
+ vector.result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector.result == NULL) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ vector.tc_id = json_integer_value(tc_id);
+ if (decode_hex(json_object_get(test, "msg"), &vector.msg,
+ &vector.msg_len) != 0 ||
+ decode_hex(json_object_get(test, "sig"), &sig, &sig_len) != 0) {
+ rte_free(vector.msg);
+ rte_free(sig);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, &vector, "acceptable result policy");
+ rte_free(vector.msg);
+ rte_free(sig);
+ continue;
+ }
+ if (sig_len != 2 * ec.bytesize) {
+ debug_vector("SKIP", name, &vector,
+ "non-canonical P1363 signature size");
+ stats->skipped_unsupported++;
+ rte_free(vector.msg);
+ rte_free(sig);
+ continue;
+ }
+ ret = run_ecdsa_verify(&ec, vector.msg, vector.msg_len, sig, sig_len / 2,
+ sig + sig_len / 2, sig_len / 2, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ } else if (strcmp(vector.result, "valid") == 0) {
+ if (ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS)
+ stats->passed++;
+ else {
+ debug_vector("FAIL", name, &vector, "valid signature rejected");
+ stats->failed++;
+ }
+ } else if (strcmp(vector.result, "invalid") == 0) {
+ if (ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS) {
+ debug_vector("FAIL", name, &vector, "invalid signature accepted");
+ stats->failed++;
+ } else {
+ stats->passed++;
+ }
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "unknown result");
+ }
+ rte_free(vector.msg);
+ rte_free(sig);
+ }
+ rte_free(ec.wx);
+ rte_free(ec.wy);
+ }
+ return 0;
+}
+
+static int
+process_file(const char *path, struct app_stats *stats)
+{
+ json_error_t error;
+ json_t *root;
+ const char *algorithm;
+ const char *schema;
+ int ret;
+
+ root = json_load_file(path, 0, &error);
+ if (root == NULL) {
+ if (env.debug)
+ printf("SKIP %s: JSON error at line %d: %s\n", path, error.line,
+ error.text);
+ return 0;
+ }
+ algorithm = json_string_value(json_object_get(root, "algorithm"));
+ schema = json_string_value(json_object_get(root, "schema"));
+ if (algorithm != NULL && schema != NULL && strcmp(algorithm, "AES-GCM") == 0 &&
+ strcmp(schema, "aead_test_schema_v1.json") == 0) {
+ printf("Processing AES-GCM vectors: %s\n", path);
+ ret = process_aead(root, RTE_CRYPTO_AEAD_AES_GCM, "AES-GCM", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "AES-CCM") == 0 &&
+ strcmp(schema, "aead_test_schema_v1.json") == 0) {
+ printf("Processing AES-CCM vectors: %s\n", path);
+ ret = process_aead(root, RTE_CRYPTO_AEAD_AES_CCM, "AES-CCM", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "CHACHA20-POLY1305") == 0 &&
+ strcmp(schema, "aead_test_schema_v1.json") == 0) {
+ printf("Processing ChaCha20-Poly1305 vectors: %s\n", path);
+ ret = process_aead(root, RTE_CRYPTO_AEAD_CHACHA20_POLY1305,
+ "ChaCha20-Poly1305", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "SM4-GCM") == 0 &&
+ strcmp(schema, "aead_test_schema_v1.json") == 0) {
+ printf("Processing SM4-GCM vectors: %s\n", path);
+ ret = process_aead(root, RTE_CRYPTO_AEAD_SM4_GCM, "SM4-GCM", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "HMACSHA256") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA256 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA256_HMAC, "HMAC-SHA256", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "HMACSHA1") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA1 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA1_HMAC, "HMAC-SHA1", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "HMACSHA224") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA224 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA224_HMAC, "HMAC-SHA224", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "HMACSHA384") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA384 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA384_HMAC, "HMAC-SHA384", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "HMACSHA512") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA512 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA512_HMAC, "HMAC-SHA512", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "HMACSHA3-224") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA3-224 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA3_224_HMAC, "HMAC-SHA3-224", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "HMACSHA3-256") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA3-256 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA3_256_HMAC, "HMAC-SHA3-256", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "HMACSHA3-384") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA3-384 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA3_384_HMAC, "HMAC-SHA3-384", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "HMACSHA3-512") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA3-512 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA3_512_HMAC, "HMAC-SHA3-512", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "HMACSM3") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SM3 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SM3_HMAC, "HMAC-SM3", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "AES-CMAC") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing AES-CMAC vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_AES_CMAC, "AES-CMAC", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "AES-GMAC") == 0 &&
+ strcmp(schema, "mac_with_iv_test_schema_v1.json") == 0) {
+ printf("Processing AES-GMAC vectors: %s\n", path);
+ ret = process_gmac(root, stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "DSA") == 0 &&
+ strcmp(schema, "dsa_p1363_verify_schema_v1.json") == 0) {
+ printf("Processing DSA (P1363) vectors: %s\n", path);
+ ret = process_dsa_p1363(root, "DSA", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "ECDH") == 0 &&
+ strcmp(schema, "ecdh_ecpoint_test_schema_v1.json") == 0) {
+ printf("Processing ECDH (ecpoint) vectors: %s\n", path);
+ ret = process_ecdh_ecpoint(root, "ECDH", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "ECDSA") == 0 &&
+ strcmp(schema, "ecdsa_p1363_verify_schema_v1.json") == 0) {
+ printf("Processing ECDSA (P1363) vectors: %s\n", path);
+ ret = process_ecdsa_p1363(root, "ECDSA", stats);
+ } else if (algorithm != NULL &&
+ (strncmp(algorithm, "AES-", strlen("AES-")) == 0 ||
+ strncmp(algorithm, "SEED-", strlen("SEED-")) == 0 ||
+ strcmp(algorithm, "SM4-CCM") == 0 ||
+ strncmp(algorithm, "HMAC", strlen("HMAC")) == 0 ||
+ strncmp(algorithm, "RSA", strlen("RSA")) == 0 ||
+ strcmp(algorithm, "DSA") == 0 ||
+ strcmp(algorithm, "ECDH") == 0 ||
+ strcmp(algorithm, "ECDSA") == 0)) {
+ debug_file("SKIP", path, algorithm);
+ stats->skipped_unsupported++;
+ ret = 0;
+ } else {
+ debug_file("SKIP", path, "unsupported vector file");
+ stats->skipped_unsupported++;
+ ret = 0;
+ }
+ json_decref(root);
+ return ret;
+}
+
+static int
+process_path(const char *path, struct app_stats *stats)
+{
+ struct stat path_stat;
+ DIR *directory;
+ struct dirent *entry;
+ int ret = 0;
+
+ if (stat(path, &path_stat) != 0)
+ return -errno;
+ if (!S_ISDIR(path_stat.st_mode))
+ return process_file(path, stats);
+
+ directory = opendir(path);
+ if (directory == NULL)
+ return -errno;
+ while ((entry = readdir(directory)) != NULL) {
+ char file_path[PATH_MAX];
+ size_t name_length = strlen(entry->d_name);
+
+ if (name_length < 6 || strcmp(entry->d_name + name_length - 5, ".json") != 0)
+ continue;
+ if (snprintf(file_path, sizeof(file_path), "%s/%s", path, entry->d_name) >=
+ (int)sizeof(file_path)) {
+ ret = -ENAMETOOLONG;
+ break;
+ }
+ ret = process_file(file_path, stats);
+ if (ret != 0)
+ break;
+ }
+ closedir(directory);
+ return ret;
+}
+
+int
+main(int argc, char **argv)
+{
+ struct app_stats stats = { 0 };
+ int eal_args;
+ int ret;
+
+ eal_args = rte_eal_init(argc, argv);
+ if (eal_args < 0)
+ rte_exit(EXIT_FAILURE, "Cannot initialize EAL\n");
+ argc -= eal_args;
+ argv += eal_args;
+
+ ret = parse_args(argc, argv);
+ if (ret != 0) {
+ usage(argv[0]);
+ ret = EXIT_FAILURE;
+ goto out_eal;
+ }
+ ret = app_init();
+ if (ret != 0) {
+ printf("Cannot initialize cryptodev %u: %s\n", env.dev_id, rte_strerror(-ret));
+ ret = EXIT_FAILURE;
+ goto out_eal;
+ }
+
+ ret = process_path(env.vectors_path, &stats);
+ if (ret != 0)
+ printf("Vector processing failed: %s\n", rte_strerror(-ret));
+ printf("Summary: passed=%" PRIu64 " failed=%" PRIu64
+ " skipped_capability=%" PRIu64 " skipped_acceptable=%" PRIu64
+ " skipped_unsupported=%" PRIu64 "\n", stats.passed, stats.failed,
+ stats.skipped_capability, stats.skipped_acceptable, stats.skipped_unsupported);
+ app_uninit();
+ ret = ret == 0 && stats.failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+
+out_eal:
+ rte_eal_cleanup();
+ return ret;
+}
diff --git a/examples/wycheproof_validation/meson.build b/examples/wycheproof_validation/meson.build
new file mode 100644
index 0000000000..7f18b4cef3
--- /dev/null
+++ b/examples/wycheproof_validation/meson.build
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026
+
+# meson file, for building this example as part of a main DPDK build.
+#
+# To build this example as a standalone application with an already-installed
+# DPDK instance, use 'make'
+
+deps += ['cryptodev']
+
+if not dpdk_conf.has('RTE_HAS_JANSSON')
+ build = false
+ message('Skipping wycheproof_validation: Jansson is required')
+else
+ ext_deps += jansson_dep
+ sources = files('main.c')
+endif
--
2.34.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH] examples: add Wycheproof validation app
2026-09-15 15:51 [PATCH] examples: add Wycheproof validation app Kai Ji
@ 2026-09-15 16:58 ` Stephen Hemminger
2026-09-15 18:28 ` Stephen Hemminger
2026-09-17 15:34 ` [PATCH v3] " Kai Ji
2 siblings, 0 replies; 17+ messages in thread
From: Stephen Hemminger @ 2026-09-15 16:58 UTC (permalink / raw)
To: Kai Ji; +Cc: dev
On Tue, 15 Sep 2026 15:51:57 +0000
Kai Ji <kai.ji@intel.com> wrote:
> +.PHONY: clean
> +clean:
> + rm -f build/$(APP) build/$(APP)-static build/$(APP)-shared
> + test -d build && rmdir -p build || true
> \ No newline at end of file
Fix you editor config all DPDK files have newline at EOF
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH] examples: add Wycheproof validation app
2026-09-15 15:51 [PATCH] examples: add Wycheproof validation app Kai Ji
2026-09-15 16:58 ` Stephen Hemminger
@ 2026-09-15 18:28 ` Stephen Hemminger
2026-09-17 15:34 ` [PATCH v3] " Kai Ji
2 siblings, 0 replies; 17+ messages in thread
From: Stephen Hemminger @ 2026-09-15 18:28 UTC (permalink / raw)
To: Kai Ji; +Cc: dev
On Tue, 15 Sep 2026 15:51:57 +0000
Kai Ji <kai.ji@intel.com> wrote:
> Add a Wycheproof JSON vector validation example for cryptodev PMDs.
>
> Support these algorithms when advertised by the selected PMD:
> - AEAD: AES-GCM, AES-CCM, SM4-GCM, ChaCha20-Poly1305
> - MAC: AES-CMAC, AES-GMAC, HMAC SHA-1/SHA-2/SHA-3/SM3
> - Asymmetric: DSA (P1363 verify), ECDSA (P1363 verify),
> ECDH (ecpoint shared-secret compute)
>
> Validate valid vectors against generated ciphertexts, tags, plaintexts,
> digests, shared secrets, or signature verification, and require the
> expected rejection for invalid vectors. Digest inputs for DSA and ECDSA
> use the symmetric auth path, selecting a separate symmetric-capable
> device when the target device is asymmetric-only.
>
> Skip parameter combinations outside PMD capability ranges and identify
> recognized vector families without a compatible DPDK transform. A
> --debug option lists every failed or skipped vector.
>
> Add Meson and standalone build integration, with usage documentation.
>
> Signed-off-by: Kai Ji <kai.ji@intel.com>
> ---
Fable found lots of issues that need addressing.
Review: [PATCH] examples: add Wycheproof validation app
Built with meson -Dwerror=true, gcc 13.3, crypto/openssl. Ran the app
against Wycheproof v1 vectors (GCM, CCM, GMAC, HMAC, CMAC, DSA) with
crypto_openssl after fixing the build error below; those pass.
Errors
------
1. Build fails with -Dwerror (test-meson-builds.sh default):
main.c:954,957: 'status' may be used uninitialized
main.c:1194: 'status' may be used uninitialized
process_gmac (decl line 929) and process_dsa_p1363 (line 1194)
leave status uninitialized on the -ENOTSUP and non-valid/invalid
paths. validate_aead_vector (decl line 740) has a real read: a
non-zero ret from run_aead jumps to failed: and line 795 prints
status. Initialize to RTE_CRYPTO_OP_STATUS_ERROR in all four
places, as process_ecdh_ecpoint and process_ecdsa_p1363 already do.
2. CCM AAD buffer is too small, heap overflow on QAT (line 526):
aad = rte_zmalloc(NULL, vector->aad_len + 18, 0);
rte_crypto_sym.h (aad_length) requires the 18-byte prefix plus
padding so the allocation is a multiple of 16, and says PMDs may
write the padding. QAT does: qat_crypto_pmd_gens.h ~1194-1206
memsets &aad_data[18 + aad_len] for 16 - (aad_len + 2) % 16 bytes.
Every CCM vector with (aad_len + 2) % 16 != 0 (Wycheproof aad
lengths 0, 1, 2, 7, 8, 15, 16, 17, ...) writes past the buffer.
app/test uses RTE_ALIGN_CEIL(aad_len + 18, 16); do the same.
The guard on the next line is also wrong:
if (aad == NULL && vector->aad_len != 0)
The allocation is never zero-size, so a NULL here means the PMD
writes B0 and the flags byte through a NULL aad.data. Use
if (aad == NULL).
Warnings
--------
3. EC operands passed at JSON width (lines 1548-1551, 1327, 1354):
xform.ec.q.x.length = group->wx_len;
xform.ec.pkey.length = priv_len;
Wycheproof encodes wx, wy and private as signed-style big integers:
in ecdsa_secp256r1_sha256_p1363_test.json most groups have a
33-byte wx or wy (leading 0x00), some 29 bytes; secp521r1 groups
range 62-66; ecdh private lengths are 1, 29, 32, 33. QAT, which the
README names for ECDSA/ECDH, packs with SET_PKE_LN/SET_PKE_9A_IN as
buf + alignsize - length with no length check
(drivers/crypto/qat/asym/qat_asym.c:81-96); a 33-byte value writes
its 0x00 one byte before the slot, into the neighbouring operand.
Result is wrong verifications, not skips. Normalize to bytesize:
strip leading zeros (value_equals_padded already does this), reject
anything still longer than bytesize, left-pad into a fixed buffer.
DSA p/q/g/y have the same encoding (a 2048-bit p is 257 bytes);
only crypto_openssl implements DSA today so it is tolerated there.
4. IV copied into the op with no bound (lines 533, 538, 711):
memcpy(rte_crypto_op_ctod_offset(op, uint8_t *, IV_OFFSET),
vector->iv, vector->iv_len);
The only guard is the PMD's advertised iv_size range. mvsam
advertises AES-GMAC iv_size max 65532 (rte_mrvl_pmd_ops.c:371);
aes_gcm_test.json and aes_ccm_test.json carry 257 and 268-byte
IVs. The room after IV_OFFSET is 152 bytes, and only because
priv_size at line 210 is IV_OFFSET + 64: priv_size is already
relative to the end of the sym op (rte_crypto_op_pool_create adds
sizeof(op) + sizeof(sym_op)), so IV_OFFSET is counted twice.
Define an IV maximum, use it as priv_size, and skip vectors whose
iv_len (+1 for CCM) exceeds it.
5. mbuf pool sizing (line 181):
sizeof(struct rte_mbuf) + RTE_PKTMBUF_HEADROOM +
env.mbuf_data_room
data_room_size already excludes the mbuf struct; this wastes 128
bytes per mbuf. The parameter is uint16_t and --mbuf-dataroom is
only checked for != 0, so large values truncate silently and
values below RTE_PKTMBUF_HEADROOM wrap the check at line 754
(env.mbuf_data_room - RTE_PKTMBUF_HEADROOM). Drop the sizeof and
range-check the option.
6. PMD failures on valid vectors counted as skips (lines 1473-1476):
} else {
stats->skipped_capability++;
debug_vector("SKIP", name, &vector, "PMD could not compute");
A valid ECDH vector where the op returns an error exits 0.
run_ecdh_ecpoint and run_ecdsa_verify also turn asym session
create failure into -ENOTSUP (lines 1341, 1556) while
run_dsa_verify returns -ENOMEM and fails the vector. Capability
skips should come only from the capability query; anything the
PMD rejects after that is a failure for a validation tool.
7. Documentation and MAINTAINERS. Examples are documented in
doc/guides/sample_app_ug/<name>.rst and listed in index.rst (see
fips_validation); README.md is not the convention (only
examples/bpf/README exists). Add the rst and a MAINTAINERS entry
with F: examples/wycheproof_validation/ and the rst, as
fips_validation has.
8. Makefile has no trailing newline (checkpatch warns).
Info
----
9. process_file is a 20-arm strcmp chain re-testing algorithm and
schema in every arm. A static table {algorithm, schema, kind,
algo id} and a loop replaces it.
10. decode_hex uses rte_malloc for every field. Only the AAD (the
GCM/ChaCha path hands vector->aad to the PMD directly) and the
digest buffers need IOVA memory; keys, messages, signatures and
DSA/EC parameters are copied by the library or PMD. If the AAD is
always copied into a 16-byte-padded rte_malloc buffer (fix 2
does this for CCM), decode_hex can use plain malloc.
11. Every dequeue loop (549, 637, 717, 1064, 1143, 1363, 1574) spins
forever if the PMD never returns the op. A bounded wait that
fails the vector is better for a tool meant to run on hardware.
12. Symbols used without a direct include: bool (<stdbool.h>),
PRIu64 (<inttypes.h>), strtoul/EXIT_* (<stdlib.h>), PATH_MAX
(<limits.h>). All come in transitively today.
13. Placement: this is a conformance tool with an exit-code contract,
not a sample showing API use. app/ next to dpdk-test-crypto-perf
is the more natural home.
14. Pre-existing, not introduced by this patch: QAT SET_PKE_LN and
SET_PKE_9A_IN (qat_asym.c:81-96) never check length <= alignsize,
so an oversize rte_crypto_uint writes before its slot. Worth a
separate fix in the PMD.
Review-Result: ERROR
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v3] examples: add Wycheproof validation app
2026-09-15 15:51 [PATCH] examples: add Wycheproof validation app Kai Ji
2026-09-15 16:58 ` Stephen Hemminger
2026-09-15 18:28 ` Stephen Hemminger
@ 2026-09-17 15:34 ` Kai Ji
2026-09-21 12:05 ` [EXTERNAL] " Gowrishankar Muthukrishnan
` (2 more replies)
2 siblings, 3 replies; 17+ messages in thread
From: Kai Ji @ 2026-09-17 15:34 UTC (permalink / raw)
To: dev; +Cc: Kai Ji, Thomas Monjalon
Add a Wycheproof JSON vector validation example for cryptodev PMDs.
Support these algorithms when advertised by the selected PMD:
- AEAD: AES-GCM, AES-CCM, SM4-GCM, ChaCha20-Poly1305
- MAC: AES-CMAC, AES-GMAC, HMAC SHA-1/SHA-2/SHA-3/SM3
- Asymmetric: DSA (P1363 verify), ECDSA (P1363 verify),
ECDH (ecpoint shared-secret compute)
Validate valid vectors against generated ciphertexts, tags, plaintexts,
digests, shared secrets, or signature verification, and require the
expected rejection for invalid vectors. Digest inputs for DSA and ECDSA
use the symmetric auth path, selecting a separate symmetric-capable
device when the target device is asymmetric-only.
Skip parameter combinations outside PMD capability ranges and identify
recognized vector families without a compatible DPDK transform. A
--debug option lists every failed or skipped vector.
Add Meson and standalone build integration, with usage documentation.
Signed-off-by: Kai Ji <kai.ji@intel.com>
---
MAINTAINERS | 5 +
doc/guides/sample_app_ug/index.rst | 1 +
.../sample_app_ug/wycheproof_validation.rst | 45 +
examples/meson.build | 1 +
examples/wycheproof_validation/Makefile | 40 +
examples/wycheproof_validation/main.c | 1982 +++++++++++++++++
examples/wycheproof_validation/meson.build | 17 +
7 files changed, 2091 insertions(+)
create mode 100644 doc/guides/sample_app_ug/wycheproof_validation.rst
create mode 100644 examples/wycheproof_validation/Makefile
create mode 100644 examples/wycheproof_validation/main.c
create mode 100644 examples/wycheproof_validation/meson.build
diff --git a/MAINTAINERS b/MAINTAINERS
index e99a65d197..c20b037b6d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2041,6 +2041,11 @@ M: Gowrishankar Muthukrishnan <gmuthukrishn@marvell.com>
F: examples/fips_validation/
F: doc/guides/sample_app_ug/fips_validation.rst
+Wycheproof validation example
+M: Kai Ji <kai.ji@intel.com>
+F: examples/wycheproof_validation/
+F: doc/guides/sample_app_ug/wycheproof_validation.rst
+
Flow filtering example
M: Ori Kam <orika@nvidia.com>
F: examples/flow_filtering/
diff --git a/doc/guides/sample_app_ug/index.rst b/doc/guides/sample_app_ug/index.rst
index f12623bb66..29313fe3de 100644
--- a/doc/guides/sample_app_ug/index.rst
+++ b/doc/guides/sample_app_ug/index.rst
@@ -53,6 +53,7 @@ Sample Applications User Guides
ptpclient
ptp_tap_relay_sw
fips_validation
+ wycheproof_validation
ipsec_secgw
bbdev_app
ntb
diff --git a/doc/guides/sample_app_ug/wycheproof_validation.rst b/doc/guides/sample_app_ug/wycheproof_validation.rst
new file mode 100644
index 0000000000..3cc90e6dd0
--- /dev/null
+++ b/doc/guides/sample_app_ug/wycheproof_validation.rst
@@ -0,0 +1,45 @@
+.. SPDX-License-Identifier: BSD-3-Clause
+ Copyright(c) 2026 Intel Corporation.
+
+Wycheproof Validation Example
+============================
+
+Overview
+--------
+
+This example validates a DPDK cryptodev implementation against the Google
+Wycheproof JSON test vectors.
+
+The application reads one JSON file or a directory of JSON files at runtime and
+checks the supported algorithm families against the selected PMD. It can be used
+with a PMD that advertises AEAD, MAC, DSA, ECDH, or ECDSA support.
+
+Build
+-----
+
+Build the example from the DPDK tree with Meson:
+
+.. code-block:: console
+
+ meson setup build -Dexamples=wycheproof_validation -Denable_drivers=crypto/openssl
+ meson compile -C build
+
+Standalone Makefile builds are also supported from the example directory.
+
+Run
+---
+
+Run the example with an OpenSSL-backed cryptodev and a vector file:
+
+.. code-block:: console
+
+ ./build/examples/dpdk-wycheproof_validation --vdev crypto_openssl -- \
+ --vectors ../wycheproof/testvectors_v1/aes_gcm_test.json \
+ --cryptodev crypto_openssl --debug
+
+The program accepts a single JSON file or a directory of JSON files. Use
+``--debug`` to print each failed or skipped vector with its reason.
+
+The exit code is nonzero when a parsed and supported vector fails validation;
+unsupported vectors, capability-skipped cases, and acceptable results are reported
+in the summary without forcing a failure.
diff --git a/examples/meson.build b/examples/meson.build
index 25d9c88457..000d05863a 100644
--- a/examples/meson.build
+++ b/examples/meson.build
@@ -59,6 +59,7 @@ all_examples = [
'vm_power_manager/guest_cli',
'vmdq',
'vmdq_dcb',
+ 'wycheproof_validation',
]
# on install, skip copying all meson.build files
diff --git a/examples/wycheproof_validation/Makefile b/examples/wycheproof_validation/Makefile
new file mode 100644
index 0000000000..8cc0293364
--- /dev/null
+++ b/examples/wycheproof_validation/Makefile
@@ -0,0 +1,40 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026
+
+APP = wycheproof_validation
+SRCS-y := main.c
+
+PKGCONF ?= pkg-config
+
+ifneq ($(shell $(PKGCONF) --exists libdpdk && echo 0),0)
+$(error "no installation of DPDK found")
+endif
+ifneq ($(shell $(PKGCONF) --exists jansson && echo 0),0)
+$(error "Jansson is required")
+endif
+
+all: shared
+.PHONY: shared static
+shared: build/$(APP)-shared
+ ln -sf $(APP)-shared build/$(APP)
+static: build/$(APP)-static
+ ln -sf $(APP)-static build/$(APP)
+
+PC_FILE := $(shell $(PKGCONF) --path libdpdk 2>/dev/null)
+CFLAGS += -O3 $(shell $(PKGCONF) --cflags libdpdk jansson)
+LDFLAGS_SHARED = $(shell $(PKGCONF) --libs libdpdk jansson)
+LDFLAGS_STATIC = $(shell $(PKGCONF) --static --libs libdpdk jansson)
+
+build/$(APP)-shared: $(SRCS-y) Makefile $(PC_FILE) | build
+ $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_SHARED)
+
+build/$(APP)-static: $(SRCS-y) Makefile $(PC_FILE) | build
+ $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_STATIC)
+
+build:
+ @mkdir -p $@
+
+.PHONY: clean
+clean:
+ rm -f build/$(APP) build/$(APP)-static build/$(APP)-shared
+ test -d build && rmdir -p build || true
diff --git a/examples/wycheproof_validation/main.c b/examples/wycheproof_validation/main.c
new file mode 100644
index 0000000000..410f8671ae
--- /dev/null
+++ b/examples/wycheproof_validation/main.c
@@ -0,0 +1,1982 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026
+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <jansson.h>
+
+#include <rte_cryptodev.h>
+#include <rte_cycles.h>
+#include <rte_eal.h>
+#include <rte_errno.h>
+#include <rte_mbuf.h>
+#include <rte_mempool.h>
+#include <rte_malloc.h>
+
+#define APP_NAME "wycheproof_validation"
+#define DEFAULT_MBUF_DATA_ROOM 16384
+#define MAX_IV_LEN 512
+#define MAX_EC_BYTESIZE 66
+#define IV_OFFSET (sizeof(struct rte_crypto_op) + sizeof(struct rte_crypto_sym_op))
+
+struct wycheproof_data {
+ uint8_t *key;
+ uint8_t *iv;
+ uint8_t *aad;
+ uint8_t *msg;
+ uint8_t *ct;
+ uint8_t *tag;
+ uint32_t key_len;
+ uint32_t iv_len;
+ uint32_t aad_len;
+ uint32_t msg_len;
+ uint32_t ct_len;
+ uint32_t tag_len;
+ json_int_t tc_id;
+ const char *result;
+};
+
+struct app_env {
+ uint8_t dev_id;
+ uint8_t hash_dev_id;
+ bool hash_dev_own;
+ uint32_t mbuf_data_room;
+ const char *vectors_path;
+ bool debug;
+ struct rte_mempool *mbuf_pool;
+ struct rte_mempool *session_pool;
+ struct rte_mempool *op_pool;
+ struct rte_mempool *asym_session_pool;
+ struct rte_mempool *asym_op_pool;
+};
+
+struct app_stats {
+ uint64_t passed;
+ uint64_t failed;
+ uint64_t skipped_capability;
+ uint64_t skipped_acceptable;
+ uint64_t skipped_unsupported;
+};
+
+static struct app_env env = {
+ .mbuf_data_room = DEFAULT_MBUF_DATA_ROOM,
+};
+
+static void
+usage(const char *program)
+{
+ printf("%s [EAL options] -- --vectors PATH [--cryptodev NAME | --cryptodev-id ID] [--debug]\n",
+ program);
+}
+
+static void
+debug_vector(const char *type, const char *algorithm,
+ const struct wycheproof_data *vector, const char *reason)
+{
+ if (env.debug)
+ printf("%s %s tcId=%" JSON_INTEGER_FORMAT " result=%s: %s\n", type,
+ algorithm, vector->tc_id, vector->result, reason);
+}
+
+static void
+debug_file(const char *type, const char *path, const char *reason)
+{
+ if (env.debug)
+ printf("%s %s: %s\n", type, path, reason);
+}
+
+static int
+parse_uint32(const char *text, uint32_t *value)
+{
+ char *end;
+ unsigned long parsed;
+
+ errno = 0;
+ parsed = strtoul(text, &end, 10);
+ if (errno != 0 || *text == '\0' || *end != '\0' || parsed > UINT32_MAX)
+ return -EINVAL;
+
+ *value = parsed;
+ return 0;
+}
+
+static int
+parse_args(int argc, char **argv)
+{
+ static const struct option options[] = {
+ { "vectors", required_argument, NULL, 'v' },
+ { "cryptodev", required_argument, NULL, 'c' },
+ { "cryptodev-id", required_argument, NULL, 'd' },
+ { "mbuf-dataroom", required_argument, NULL, 'm' },
+ { "debug", no_argument, NULL, 'D' },
+ { NULL, 0, NULL, 0 },
+ };
+ bool have_device = false;
+ int option;
+
+ if (rte_cryptodev_count() == 0)
+ return -ENODEV;
+ env.dev_id = 0;
+
+ while ((option = getopt_long(argc, argv, "v:c:d:m:D", options, NULL)) != -1) {
+ uint32_t value;
+ int dev_id;
+
+ switch (option) {
+ case 'v':
+ env.vectors_path = optarg;
+ break;
+ case 'c':
+ dev_id = rte_cryptodev_get_dev_id(optarg);
+ if (dev_id < 0)
+ return dev_id;
+ env.dev_id = dev_id;
+ have_device = true;
+ break;
+ case 'd':
+ if (parse_uint32(optarg, &value) != 0 ||
+ !rte_cryptodev_is_valid_dev(value))
+ return -EINVAL;
+ env.dev_id = value;
+ have_device = true;
+ break;
+ case 'm':
+ if (parse_uint32(optarg, &env.mbuf_data_room) != 0 ||
+ env.mbuf_data_room < RTE_PKTMBUF_HEADROOM ||
+ env.mbuf_data_room > UINT16_MAX)
+ return -EINVAL;
+ break;
+ case 'D':
+ env.debug = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ if (env.vectors_path == NULL)
+ return -EINVAL;
+ if (!have_device)
+ printf("Using cryptodev %u (%s)\n", env.dev_id,
+ rte_cryptodev_name_get(env.dev_id));
+
+ return 0;
+}
+
+static int
+app_init(void)
+{
+ struct rte_cryptodev_config config = { rte_socket_id(), 1, 0 };
+ struct rte_cryptodev_qp_conf queue_pair = { 128, NULL };
+ uint32_t session_size;
+ int ret;
+
+ ret = rte_cryptodev_configure(env.dev_id, &config);
+ if (ret < 0)
+ return ret;
+
+ env.mbuf_pool = rte_pktmbuf_pool_create("WYCHEPROOF_MBUF_POOL", 64, 0, 0,
+ env.mbuf_data_room, rte_socket_id());
+ if (env.mbuf_pool == NULL) {
+ ret = -rte_errno;
+ goto error;
+ }
+
+ session_size = rte_cryptodev_sym_get_private_session_size(env.dev_id);
+ {
+ /* The hashing device may differ from the target device, so size the
+ * session pool for the largest sym session across all devices.
+ */
+ uint8_t d;
+
+ for (d = 0; d < rte_cryptodev_count(); d++) {
+ uint32_t s = rte_cryptodev_sym_get_private_session_size(d);
+
+ if (s > session_size)
+ session_size = s;
+ }
+ }
+ env.session_pool = rte_cryptodev_sym_session_pool_create("WYCHEPROOF_SESSION_POOL",
+ 64, session_size, 0, 0, rte_socket_id());
+ if (env.session_pool == NULL) {
+ ret = -rte_errno;
+ goto error;
+ }
+
+ /* priv_size is measured from the end of the sym op; the IV lives there. */
+ env.op_pool = rte_crypto_op_pool_create("WYCHEPROOF_OP_POOL",
+ RTE_CRYPTO_OP_TYPE_SYMMETRIC, 64, 0, MAX_IV_LEN, rte_socket_id());
+ if (env.op_pool == NULL) {
+ ret = -rte_errno;
+ goto error;
+ }
+
+ /* Asymmetric pools are best-effort; DSA/ECDSA paths gate on the
+ * device asymmetric feature flag at runtime.
+ */
+ env.asym_session_pool = rte_cryptodev_asym_session_pool_create(
+ "WYCHEPROOF_ASYM_SESS", 16, 0, 0, rte_socket_id());
+ env.asym_op_pool = rte_crypto_op_pool_create("WYCHEPROOF_ASYM_OP",
+ RTE_CRYPTO_OP_TYPE_ASYMMETRIC, 64, 0, 0, rte_socket_id());
+
+ queue_pair.mp_session = env.session_pool;
+ ret = rte_cryptodev_queue_pair_setup(env.dev_id, 0, &queue_pair,
+ rte_socket_id());
+ if (ret < 0)
+ goto error;
+
+ ret = rte_cryptodev_start(env.dev_id);
+ if (ret < 0)
+ goto error;
+
+ /* Digest computation for DSA/ECDSA needs a symmetric-auth device. The
+ * target device may be asym-only (for example QAT), so pick a separate
+ * sym-capable device for hashing when needed.
+ */
+ env.hash_dev_id = env.dev_id;
+ env.hash_dev_own = false;
+ {
+ struct rte_cryptodev_sym_capability_idx hash_idx = {
+ .type = RTE_CRYPTO_SYM_XFORM_AUTH,
+ .algo.auth = RTE_CRYPTO_AUTH_SHA256,
+ };
+ uint8_t count = rte_cryptodev_count();
+ uint8_t d;
+
+ if (rte_cryptodev_sym_capability_get(env.dev_id, &hash_idx) == NULL) {
+ for (d = 0; d < count; d++) {
+ struct rte_cryptodev_qp_conf hash_qp = { 128, NULL };
+
+ if (d == env.dev_id)
+ continue;
+ if (rte_cryptodev_sym_capability_get(d, &hash_idx) == NULL)
+ continue;
+ if (rte_cryptodev_configure(d, &config) < 0)
+ continue;
+ hash_qp.mp_session = env.session_pool;
+ if (rte_cryptodev_queue_pair_setup(d, 0, &hash_qp,
+ rte_socket_id()) < 0 ||
+ rte_cryptodev_start(d) < 0) {
+ rte_cryptodev_close(d);
+ continue;
+ }
+ env.hash_dev_id = d;
+ env.hash_dev_own = true;
+ break;
+ }
+ }
+ }
+
+ return 0;
+
+error:
+ rte_mempool_free(env.asym_op_pool);
+ rte_mempool_free(env.asym_session_pool);
+ rte_mempool_free(env.op_pool);
+ rte_mempool_free(env.session_pool);
+ rte_mempool_free(env.mbuf_pool);
+ env.asym_op_pool = NULL;
+ env.asym_session_pool = NULL;
+ env.op_pool = NULL;
+ env.session_pool = NULL;
+ env.mbuf_pool = NULL;
+ return ret;
+}
+
+static void
+app_uninit(void)
+{
+ if (env.hash_dev_own) {
+ rte_cryptodev_stop(env.hash_dev_id);
+ rte_cryptodev_close(env.hash_dev_id);
+ }
+ rte_cryptodev_stop(env.dev_id);
+ rte_cryptodev_close(env.dev_id);
+ rte_mempool_free(env.asym_op_pool);
+ rte_mempool_free(env.asym_session_pool);
+ rte_mempool_free(env.op_pool);
+ rte_mempool_free(env.session_pool);
+ rte_mempool_free(env.mbuf_pool);
+}
+
+static void
+free_vector(struct wycheproof_data *vector)
+{
+ rte_free(vector->key);
+ rte_free(vector->iv);
+ rte_free(vector->aad);
+ rte_free(vector->msg);
+ rte_free(vector->ct);
+ rte_free(vector->tag);
+ memset(vector, 0, sizeof(*vector));
+}
+
+static int
+hex_value(char character)
+{
+ if (character >= '0' && character <= '9')
+ return character - '0';
+ if (character >= 'a' && character <= 'f')
+ return character - 'a' + 10;
+ if (character >= 'A' && character <= 'F')
+ return character - 'A' + 10;
+ return -1;
+}
+
+static int
+decode_hex(json_t *value, uint8_t **buffer, uint32_t *length)
+{
+ const char *hex;
+ size_t hex_length;
+ uint8_t *decoded;
+ size_t index;
+
+ if (!json_is_string(value))
+ return -EINVAL;
+
+ hex = json_string_value(value);
+ hex_length = strlen(hex);
+ if ((hex_length & 1) != 0 || hex_length / 2 > UINT32_MAX)
+ return -EINVAL;
+
+ *length = hex_length / 2;
+ if (*length == 0)
+ return 0;
+
+ decoded = rte_malloc(NULL, *length, 0);
+ if (decoded == NULL)
+ return -ENOMEM;
+
+ for (index = 0; index < *length; index++) {
+ int high = hex_value(hex[index * 2]);
+ int low = hex_value(hex[index * 2 + 1]);
+
+ if (high < 0 || low < 0) {
+ rte_free(decoded);
+ return -EINVAL;
+ }
+ decoded[index] = (high << 4) | low;
+ }
+
+ *buffer = decoded;
+ return 0;
+}
+
+static int
+parse_vector(json_t *test, struct wycheproof_data *vector)
+{
+ json_t *tc_id;
+ int ret;
+
+ memset(vector, 0, sizeof(*vector));
+ tc_id = json_object_get(test, "tcId");
+ vector->result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector->result == NULL)
+ return -EINVAL;
+ vector->tc_id = json_integer_value(tc_id);
+
+ ret = decode_hex(json_object_get(test, "key"), &vector->key, &vector->key_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "iv"), &vector->iv, &vector->iv_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "aad"), &vector->aad, &vector->aad_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "msg"), &vector->msg, &vector->msg_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "ct"), &vector->ct, &vector->ct_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "tag"), &vector->tag, &vector->tag_len);
+ if (ret != 0)
+ goto error;
+
+ return 0;
+
+error:
+ free_vector(vector);
+ return ret;
+}
+
+static int
+parse_mac_vector(json_t *test, struct wycheproof_data *vector)
+{
+ json_t *tc_id;
+ int ret;
+
+ memset(vector, 0, sizeof(*vector));
+ tc_id = json_object_get(test, "tcId");
+ vector->result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector->result == NULL)
+ return -EINVAL;
+ vector->tc_id = json_integer_value(tc_id);
+
+ ret = decode_hex(json_object_get(test, "key"), &vector->key, &vector->key_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "msg"), &vector->msg, &vector->msg_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "tag"), &vector->tag, &vector->tag_len);
+ if (ret != 0)
+ goto error;
+
+ return 0;
+
+error:
+ free_vector(vector);
+ return ret;
+}
+
+static int
+parse_mac_with_iv_vector(json_t *test, struct wycheproof_data *vector)
+{
+ int ret;
+
+ ret = parse_mac_vector(test, vector);
+ if (ret != 0)
+ return ret;
+ ret = decode_hex(json_object_get(test, "iv"), &vector->iv, &vector->iv_len);
+ if (ret != 0)
+ free_vector(vector);
+ return ret;
+}
+
+/* Bounded wait for one completed op; NULL on timeout so a stuck PMD fails the
+ * vector instead of hanging the tool.
+ */
+static struct rte_crypto_op *
+dequeue_one(uint8_t dev_id)
+{
+ struct rte_crypto_op *completed = NULL;
+ uint64_t deadline = rte_get_timer_cycles() + rte_get_timer_hz();
+
+ while (rte_cryptodev_dequeue_burst(dev_id, 0, &completed, 1) == 0) {
+ if (rte_get_timer_cycles() > deadline)
+ return NULL;
+ rte_pause();
+ }
+ return completed;
+}
+
+static int
+check_aead_capability(const struct wycheproof_data *vector,
+ enum rte_crypto_aead_algorithm algorithm)
+{
+ struct rte_cryptodev_sym_capability_idx index = {
+ .type = RTE_CRYPTO_SYM_XFORM_AEAD,
+ .algo.aead = algorithm,
+ };
+ const struct rte_cryptodev_symmetric_capability *capability;
+
+ capability = rte_cryptodev_sym_capability_get(env.dev_id, &index);
+ if (capability == NULL)
+ return -ENOTSUP;
+
+ return rte_cryptodev_sym_capability_check_aead(capability, vector->key_len,
+ vector->tag_len, vector->aad_len, vector->iv_len);
+}
+
+static int
+run_aead(const struct wycheproof_data *vector, enum rte_crypto_aead_algorithm algorithm,
+ enum rte_crypto_aead_operation operation, uint8_t **output, uint8_t **digest,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_sym_xform xform = { 0 };
+ struct rte_cryptodev_sym_session *session = NULL;
+ struct rte_crypto_op *operation_op = NULL;
+ struct rte_mbuf *mbuf = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_crypto_sym_op *sym_op;
+ uint8_t *aad = NULL;
+ uint8_t *input;
+ uint32_t input_len;
+ int ret = -ENOMEM;
+
+ *output = NULL;
+ *digest = NULL;
+ xform.type = RTE_CRYPTO_SYM_XFORM_AEAD;
+ xform.aead.algo = algorithm;
+ xform.aead.op = operation;
+ xform.aead.key.data = vector->key;
+ xform.aead.key.length = vector->key_len;
+ xform.aead.iv.offset = IV_OFFSET;
+ xform.aead.iv.length = vector->iv_len;
+ xform.aead.aad_length = vector->aad_len;
+ xform.aead.digest_length = vector->tag_len;
+
+ session = rte_cryptodev_sym_session_create(env.dev_id, &xform, env.session_pool);
+ if (session == NULL)
+ goto out;
+
+ operation_op = rte_crypto_op_alloc(env.op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC);
+ if (operation_op == NULL)
+ goto out;
+
+ mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
+ if (mbuf == NULL)
+ goto out;
+ input = operation == RTE_CRYPTO_AEAD_OP_ENCRYPT ? vector->msg : vector->ct;
+ input_len = operation == RTE_CRYPTO_AEAD_OP_ENCRYPT ? vector->msg_len : vector->ct_len;
+ if (input_len > rte_pktmbuf_tailroom(mbuf)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ if (input_len != 0)
+ memcpy(rte_pktmbuf_append(mbuf, input_len), input, input_len);
+
+ *digest = rte_malloc(NULL, vector->tag_len, RTE_CACHE_LINE_SIZE);
+ if (*digest == NULL && vector->tag_len != 0)
+ goto out;
+ if (operation == RTE_CRYPTO_AEAD_OP_DECRYPT && vector->tag_len != 0)
+ memcpy(*digest, vector->tag, vector->tag_len);
+
+ sym_op = operation_op->sym;
+ sym_op->m_src = mbuf;
+ sym_op->aead.data.offset = 0;
+ sym_op->aead.data.length = input_len;
+ if (algorithm == RTE_CRYPTO_AEAD_AES_CCM) {
+ aad = rte_zmalloc(NULL, RTE_ALIGN_CEIL(vector->aad_len + 18, 16), 0);
+ if (aad == NULL)
+ goto out;
+ if (vector->aad_len != 0)
+ memcpy(aad + 18, vector->aad, vector->aad_len);
+ sym_op->aead.aad.data = aad;
+ sym_op->aead.aad.phys_addr = rte_malloc_virt2iova(aad);
+ memcpy(rte_crypto_op_ctod_offset(operation_op, uint8_t *, IV_OFFSET) + 1,
+ vector->iv, vector->iv_len);
+ } else {
+ sym_op->aead.aad.data = vector->aad;
+ sym_op->aead.aad.phys_addr = rte_malloc_virt2iova(vector->aad);
+ memcpy(rte_crypto_op_ctod_offset(operation_op, uint8_t *, IV_OFFSET), vector->iv,
+ vector->iv_len);
+ }
+ sym_op->aead.digest.data = *digest;
+ sym_op->aead.digest.phys_addr = rte_malloc_virt2iova(*digest);
+ rte_crypto_op_attach_sym_session(operation_op, session);
+
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &operation_op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL) {
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+
+ *status = completed->status;
+ if (*status == RTE_CRYPTO_OP_STATUS_SUCCESS && input_len != 0) {
+ *output = rte_malloc(NULL, input_len, 0);
+ if (*output == NULL)
+ goto out;
+ memcpy(*output, rte_pktmbuf_mtod(mbuf, uint8_t *), input_len);
+ }
+ ret = 0;
+
+out:
+ rte_free(aad);
+ rte_pktmbuf_free(mbuf);
+ rte_crypto_op_free(operation_op);
+ rte_cryptodev_sym_session_free(env.dev_id, session);
+ if (ret != 0) {
+ rte_free(*output);
+ rte_free(*digest);
+ *output = NULL;
+ *digest = NULL;
+ }
+ return ret;
+}
+
+static int
+run_hmac(const struct wycheproof_data *vector, enum rte_crypto_auth_algorithm algorithm,
+ enum rte_crypto_auth_operation operation, uint8_t **digest,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_sym_xform xform = { 0 };
+ struct rte_cryptodev_sym_session *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_mbuf *mbuf = NULL;
+ const struct rte_cryptodev_symmetric_capability *capability;
+ struct rte_cryptodev_sym_capability_idx index = {
+ .type = RTE_CRYPTO_SYM_XFORM_AUTH,
+ .algo.auth = algorithm,
+ };
+ int ret = -ENOMEM;
+
+ *digest = NULL;
+ capability = rte_cryptodev_sym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_sym_capability_check_auth(capability,
+ vector->key_len, vector->tag_len, 0) != 0)
+ return -ENOTSUP;
+
+ xform.type = RTE_CRYPTO_SYM_XFORM_AUTH;
+ xform.auth.algo = algorithm;
+ xform.auth.op = operation;
+ xform.auth.key.data = vector->key;
+ xform.auth.key.length = vector->key_len;
+ xform.auth.digest_length = vector->tag_len;
+ session = rte_cryptodev_sym_session_create(env.dev_id, &xform, env.session_pool);
+ if (session == NULL)
+ goto out;
+
+ op = rte_crypto_op_alloc(env.op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC);
+ if (op == NULL)
+ goto out;
+ mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
+ if (mbuf == NULL)
+ goto out;
+ if (vector->msg_len > rte_pktmbuf_tailroom(mbuf)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ if (vector->msg_len != 0)
+ memcpy(rte_pktmbuf_append(mbuf, vector->msg_len), vector->msg, vector->msg_len);
+
+ *digest = rte_malloc(NULL, vector->tag_len, RTE_CACHE_LINE_SIZE);
+ if (*digest == NULL && vector->tag_len != 0)
+ goto out;
+ if (operation == RTE_CRYPTO_AUTH_OP_VERIFY && vector->tag_len != 0)
+ memcpy(*digest, vector->tag, vector->tag_len);
+
+ op->sym->m_src = mbuf;
+ op->sym->auth.data.offset = 0;
+ op->sym->auth.data.length = vector->msg_len;
+ op->sym->auth.digest.data = *digest;
+ op->sym->auth.digest.phys_addr = rte_malloc_virt2iova(*digest);
+ rte_crypto_op_attach_sym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL) {
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+ *status = completed->status;
+ ret = 0;
+
+out:
+ rte_pktmbuf_free(mbuf);
+ rte_crypto_op_free(op);
+ rte_cryptodev_sym_session_free(env.dev_id, session);
+ if (ret != 0) {
+ rte_free(*digest);
+ *digest = NULL;
+ }
+ return ret;
+}
+
+static int
+run_gmac(const struct wycheproof_data *vector, enum rte_crypto_auth_operation operation,
+ uint8_t **digest, enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_sym_xform xform = { 0 };
+ struct rte_cryptodev_sym_session *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_mbuf *mbuf = NULL;
+ const struct rte_cryptodev_symmetric_capability *capability;
+ struct rte_cryptodev_sym_capability_idx index = {
+ .type = RTE_CRYPTO_SYM_XFORM_AUTH,
+ .algo.auth = RTE_CRYPTO_AUTH_AES_GMAC,
+ };
+ int ret = -ENOMEM;
+
+ *digest = NULL;
+ capability = rte_cryptodev_sym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_sym_capability_check_auth(capability,
+ vector->key_len, vector->tag_len, vector->iv_len) != 0)
+ return -ENOTSUP;
+
+ xform.type = RTE_CRYPTO_SYM_XFORM_AUTH;
+ xform.auth.algo = RTE_CRYPTO_AUTH_AES_GMAC;
+ xform.auth.op = operation;
+ xform.auth.key.data = vector->key;
+ xform.auth.key.length = vector->key_len;
+ xform.auth.iv.offset = IV_OFFSET;
+ xform.auth.iv.length = vector->iv_len;
+ xform.auth.digest_length = vector->tag_len;
+ session = rte_cryptodev_sym_session_create(env.dev_id, &xform, env.session_pool);
+ if (session == NULL)
+ goto out;
+
+ op = rte_crypto_op_alloc(env.op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC);
+ if (op == NULL)
+ goto out;
+ mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
+ if (mbuf == NULL)
+ goto out;
+ if (vector->msg_len > rte_pktmbuf_tailroom(mbuf)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ if (vector->msg_len != 0)
+ memcpy(rte_pktmbuf_append(mbuf, vector->msg_len), vector->msg, vector->msg_len);
+
+ *digest = rte_malloc(NULL, vector->tag_len, RTE_CACHE_LINE_SIZE);
+ if (*digest == NULL && vector->tag_len != 0)
+ goto out;
+ if (operation == RTE_CRYPTO_AUTH_OP_VERIFY && vector->tag_len != 0)
+ memcpy(*digest, vector->tag, vector->tag_len);
+
+ op->sym->m_src = mbuf;
+ op->sym->auth.data.offset = 0;
+ op->sym->auth.data.length = vector->msg_len;
+ op->sym->auth.digest.data = *digest;
+ op->sym->auth.digest.phys_addr = rte_malloc_virt2iova(*digest);
+ memcpy(rte_crypto_op_ctod_offset(op, uint8_t *, IV_OFFSET), vector->iv, vector->iv_len);
+ rte_crypto_op_attach_sym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL) {
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+ *status = completed->status;
+ ret = 0;
+
+out:
+ rte_pktmbuf_free(mbuf);
+ rte_crypto_op_free(op);
+ rte_cryptodev_sym_session_free(env.dev_id, session);
+ if (ret != 0) {
+ rte_free(*digest);
+ *digest = NULL;
+ }
+ return ret;
+}
+
+static void
+validate_aead_vector(const struct wycheproof_data *vector,
+ enum rte_crypto_aead_algorithm algorithm, const char *name,
+ struct app_stats *stats)
+{
+ uint8_t *output = NULL;
+ uint8_t *digest = NULL;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ int ret;
+
+ if (strcmp(vector->result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, vector, "acceptable result policy");
+ return;
+ }
+ if (strcmp(vector->result, "valid") != 0 && strcmp(vector->result, "invalid") != 0) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, vector, "unknown result");
+ return;
+ }
+ /* CCM writes the IV one byte into the reserved area (after the flags byte). */
+ if (vector->iv_len + (algorithm == RTE_CRYPTO_AEAD_AES_CCM ? 1u : 0u) > MAX_IV_LEN ||
+ vector->msg_len != vector->ct_len ||
+ vector->msg_len > env.mbuf_data_room) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, vector, "unsupported message length");
+ return;
+ }
+ if (check_aead_capability(vector, algorithm) != 0) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, vector, "PMD capability");
+ return;
+ }
+
+ if (strcmp(vector->result, "valid") == 0) {
+ ret = run_aead(vector, algorithm, RTE_CRYPTO_AEAD_OP_ENCRYPT, &output, &digest,
+ &status);
+ if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS ||
+ memcmp(output, vector->ct, vector->ct_len) != 0 ||
+ memcmp(digest, vector->tag, vector->tag_len) != 0)
+ goto failed;
+ rte_free(output);
+ rte_free(digest);
+ output = NULL;
+ digest = NULL;
+ ret = run_aead(vector, algorithm, RTE_CRYPTO_AEAD_OP_DECRYPT, &output, &digest,
+ &status);
+ if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS ||
+ memcmp(output, vector->msg, vector->msg_len) != 0)
+ goto failed;
+ } else {
+ ret = run_aead(vector, algorithm, RTE_CRYPTO_AEAD_OP_DECRYPT, &output, &digest,
+ &status);
+ if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_AUTH_FAILED)
+ goto failed;
+ }
+
+ stats->passed++;
+ rte_free(output);
+ rte_free(digest);
+ return;
+
+failed:
+ if (env.debug)
+ printf("FAIL %s tcId=%" JSON_INTEGER_FORMAT " result=%s status=%d ret=%d\n",
+ name, vector->tc_id, vector->result, status, ret);
+ stats->failed++;
+ rte_free(output);
+ rte_free(digest);
+}
+
+static int
+process_aead(json_t *root, enum rte_crypto_aead_algorithm algorithm,
+ const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+
+ json_array_foreach(groups, group_index, group) {
+ json_t *tests = json_object_get(group, "tests");
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_array(tests))
+ return -EINVAL;
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ int ret = parse_vector(test, &vector);
+
+ if (ret != 0) {
+ if (env.debug)
+ printf("SKIP %s group=%zu test=%zu: malformed vector\n",
+ name, group_index, test_index);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ validate_aead_vector(&vector, algorithm, name, stats);
+ free_vector(&vector);
+ }
+ }
+
+ return 0;
+}
+
+static int
+process_hmac(json_t *root, enum rte_crypto_auth_algorithm algorithm,
+ const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *tests = json_object_get(group, "tests");
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_array(tests))
+ return -EINVAL;
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *digest = NULL;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ int ret;
+
+ ret = parse_mac_vector(test, &vector);
+ if (ret != 0) {
+ if (env.debug)
+ printf("SKIP %s group=%zu test=%zu: malformed vector\n",
+ name, group_index, test_index);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, &vector, "acceptable result policy");
+ } else if (strcmp(vector.result, "valid") == 0) {
+ ret = run_hmac(&vector, algorithm, RTE_CRYPTO_AUTH_OP_GENERATE,
+ &digest, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ } else if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS ||
+ memcmp(digest, vector.tag, vector.tag_len) != 0) {
+ debug_vector("FAIL", name, &vector, "generated tag mismatch");
+ stats->failed++;
+ } else
+ stats->passed++;
+ } else if (strcmp(vector.result, "invalid") == 0) {
+ ret = run_hmac(&vector, algorithm, RTE_CRYPTO_AUTH_OP_VERIFY,
+ &digest, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ } else if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_AUTH_FAILED) {
+ debug_vector("FAIL", name, &vector, "authentication unexpectedly succeeded");
+ stats->failed++;
+ } else
+ stats->passed++;
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "unknown result");
+ }
+ rte_free(digest);
+ free_vector(&vector);
+ }
+ }
+ return 0;
+}
+
+static int
+process_gmac(json_t *root, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *tests = json_object_get(group, "tests");
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_array(tests))
+ return -EINVAL;
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *digest = NULL;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ int ret;
+
+ ret = parse_mac_with_iv_vector(test, &vector);
+ if (ret != 0) {
+ if (env.debug)
+ printf("SKIP AES-GMAC group=%zu test=%zu: malformed vector\n",
+ group_index, test_index);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (vector.iv_len > MAX_IV_LEN) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", "AES-GMAC", &vector, "IV too large");
+ rte_free(digest);
+ free_vector(&vector);
+ continue;
+ }
+ if (strcmp(vector.result, "valid") == 0)
+ ret = run_gmac(&vector, RTE_CRYPTO_AUTH_OP_GENERATE,
+ &digest, &status);
+ else if (strcmp(vector.result, "invalid") == 0)
+ ret = run_gmac(&vector, RTE_CRYPTO_AUTH_OP_VERIFY,
+ &digest, &status);
+ else {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", "AES-GMAC", &vector, "acceptable result policy");
+ ret = 0;
+ }
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", "AES-GMAC", &vector, "PMD capability");
+ } else if ((strcmp(vector.result, "valid") == 0 &&
+ (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS ||
+ memcmp(digest, vector.tag, vector.tag_len) != 0)) ||
+ (strcmp(vector.result, "invalid") == 0 &&
+ (ret != 0 || status != RTE_CRYPTO_OP_STATUS_AUTH_FAILED))) {
+ debug_vector("FAIL", "AES-GMAC", &vector, "unexpected operation result");
+ stats->failed++;
+ } else if (strcmp(vector.result, "valid") == 0 ||
+ strcmp(vector.result, "invalid") == 0)
+ stats->passed++;
+ rte_free(digest);
+ free_vector(&vector);
+ }
+ }
+ return 0;
+}
+
+struct dsa_group {
+ uint8_t *p;
+ uint8_t *q;
+ uint8_t *g;
+ uint8_t *y;
+ uint32_t p_len;
+ uint32_t q_len;
+ uint32_t g_len;
+ uint32_t y_len;
+ enum rte_crypto_auth_algorithm hash;
+ uint32_t digest_len;
+};
+
+static int
+map_sha(const char *name, enum rte_crypto_auth_algorithm *algo, uint32_t *digest_len)
+{
+ if (strcmp(name, "SHA-1") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA1;
+ *digest_len = 20;
+ } else if (strcmp(name, "SHA-224") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA224;
+ *digest_len = 28;
+ } else if (strcmp(name, "SHA-256") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA256;
+ *digest_len = 32;
+ } else if (strcmp(name, "SHA-384") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA384;
+ *digest_len = 48;
+ } else if (strcmp(name, "SHA-512") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA512;
+ *digest_len = 64;
+ } else {
+ return -ENOTSUP;
+ }
+ return 0;
+}
+
+/* Compute a plain (keyless) message digest through the cryptodev auth path. */
+static int
+compute_hash(const uint8_t *msg, uint32_t msg_len, enum rte_crypto_auth_algorithm algo,
+ uint8_t *digest, uint32_t digest_len)
+{
+ struct rte_crypto_sym_xform xform = { 0 };
+ struct rte_cryptodev_sym_session *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_mbuf *mbuf = NULL;
+ const struct rte_cryptodev_symmetric_capability *capability;
+ struct rte_cryptodev_sym_capability_idx index = {
+ .type = RTE_CRYPTO_SYM_XFORM_AUTH,
+ .algo.auth = algo,
+ };
+ uint8_t *op_digest = NULL;
+ int ret = -ENOMEM;
+
+ capability = rte_cryptodev_sym_capability_get(env.hash_dev_id, &index);
+ if (capability == NULL || rte_cryptodev_sym_capability_check_auth(capability,
+ 0, digest_len, 0) != 0)
+ return -ENOTSUP;
+
+ xform.type = RTE_CRYPTO_SYM_XFORM_AUTH;
+ xform.auth.algo = algo;
+ xform.auth.op = RTE_CRYPTO_AUTH_OP_GENERATE;
+ xform.auth.digest_length = digest_len;
+ session = rte_cryptodev_sym_session_create(env.hash_dev_id, &xform, env.session_pool);
+ if (session == NULL)
+ goto out;
+
+ op = rte_crypto_op_alloc(env.op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC);
+ if (op == NULL)
+ goto out;
+ mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
+ if (mbuf == NULL)
+ goto out;
+ if (msg_len > rte_pktmbuf_tailroom(mbuf)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ if (msg_len != 0)
+ memcpy(rte_pktmbuf_append(mbuf, msg_len), msg, msg_len);
+
+ op_digest = rte_malloc(NULL, digest_len, RTE_CACHE_LINE_SIZE);
+ if (op_digest == NULL)
+ goto out;
+ op->sym->m_src = mbuf;
+ op->sym->auth.data.offset = 0;
+ op->sym->auth.data.length = msg_len;
+ op->sym->auth.digest.data = op_digest;
+ op->sym->auth.digest.phys_addr = rte_malloc_virt2iova(op_digest);
+ rte_crypto_op_attach_sym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.hash_dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.hash_dev_id);
+ if (completed == NULL) {
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+ if (completed->status != RTE_CRYPTO_OP_STATUS_SUCCESS) {
+ ret = -EIO;
+ goto out;
+ }
+ memcpy(digest, op_digest, digest_len);
+ ret = 0;
+
+out:
+ rte_free(op_digest);
+ rte_pktmbuf_free(mbuf);
+ rte_crypto_op_free(op);
+ rte_cryptodev_sym_session_free(env.hash_dev_id, session);
+ return ret;
+}
+
+static int
+run_dsa_verify(const struct dsa_group *group, const uint8_t *msg, uint32_t msg_len,
+ const uint8_t *r, uint32_t r_len, const uint8_t *s, uint32_t s_len,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_asym_xform xform = { 0 };
+ void *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_cryptodev_info dev_info;
+ struct rte_cryptodev_asym_capability_idx index = {
+ .type = RTE_CRYPTO_ASYM_XFORM_DSA,
+ };
+ const struct rte_cryptodev_asymmetric_xform_capability *capability;
+ uint8_t digest[64];
+ int ret;
+
+ if (env.asym_session_pool == NULL || env.asym_op_pool == NULL)
+ return -ENOTSUP;
+ rte_cryptodev_info_get(env.dev_id, &dev_info);
+ if ((dev_info.feature_flags & RTE_CRYPTODEV_FF_ASYMMETRIC_CRYPTO) == 0)
+ return -ENOTSUP;
+ capability = rte_cryptodev_asym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_asym_xform_capability_check_optype(capability,
+ RTE_CRYPTO_ASYM_OP_VERIFY) == 0)
+ return -ENOTSUP;
+
+ ret = compute_hash(msg, msg_len, group->hash, digest, group->digest_len);
+ if (ret != 0)
+ return ret;
+
+ xform.xform_type = RTE_CRYPTO_ASYM_XFORM_DSA;
+ xform.next = NULL;
+ xform.dsa.p.data = group->p;
+ xform.dsa.p.length = group->p_len;
+ xform.dsa.q.data = group->q;
+ xform.dsa.q.length = group->q_len;
+ xform.dsa.g.data = group->g;
+ xform.dsa.g.length = group->g_len;
+
+ ret = -ENOMEM;
+ if (rte_cryptodev_asym_session_create(env.dev_id, &xform, env.asym_session_pool,
+ &session) < 0 || session == NULL)
+ goto out;
+ op = rte_crypto_op_alloc(env.asym_op_pool, RTE_CRYPTO_OP_TYPE_ASYMMETRIC);
+ if (op == NULL)
+ goto out;
+ op->asym->dsa.op_type = RTE_CRYPTO_ASYM_OP_VERIFY;
+ /* DSA verify operates on the message digest. */
+ op->asym->dsa.message.data = digest;
+ op->asym->dsa.message.length = group->digest_len;
+ op->asym->dsa.r.data = (uint8_t *)(uintptr_t)r;
+ op->asym->dsa.r.length = r_len;
+ op->asym->dsa.s.data = (uint8_t *)(uintptr_t)s;
+ op->asym->dsa.s.length = s_len;
+ op->asym->dsa.y.data = group->y;
+ op->asym->dsa.y.length = group->y_len;
+ rte_crypto_op_attach_asym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL) {
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+ *status = completed->status;
+ ret = 0;
+
+out:
+ rte_crypto_op_free(op);
+ if (session != NULL)
+ rte_cryptodev_asym_session_free(env.dev_id, session);
+ return ret;
+}
+
+static int
+process_dsa_p1363(json_t *root, const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *pub = json_object_get(group, "publicKey");
+ json_t *tests = json_object_get(group, "tests");
+ struct dsa_group dsa = { 0 };
+ const char *sha;
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_object(pub) || !json_is_array(tests))
+ return -EINVAL;
+ sha = json_string_value(json_object_get(group, "sha"));
+ if (sha == NULL || map_sha(sha, &dsa.hash, &dsa.digest_len) != 0) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (decode_hex(json_object_get(pub, "p"), &dsa.p, &dsa.p_len) != 0 ||
+ decode_hex(json_object_get(pub, "q"), &dsa.q, &dsa.q_len) != 0 ||
+ decode_hex(json_object_get(pub, "g"), &dsa.g, &dsa.g_len) != 0 ||
+ decode_hex(json_object_get(pub, "y"), &dsa.y, &dsa.y_len) != 0) {
+ rte_free(dsa.p);
+ rte_free(dsa.q);
+ rte_free(dsa.g);
+ rte_free(dsa.y);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *sig = NULL;
+ uint32_t sig_len = 0;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ json_t *tc_id;
+ int ret;
+
+ memset(&vector, 0, sizeof(vector));
+ tc_id = json_object_get(test, "tcId");
+ vector.result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector.result == NULL) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ vector.tc_id = json_integer_value(tc_id);
+ if (decode_hex(json_object_get(test, "msg"), &vector.msg,
+ &vector.msg_len) != 0 ||
+ decode_hex(json_object_get(test, "sig"),
+ &sig, &sig_len) != 0) {
+ rte_free(vector.msg);
+ rte_free(sig);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, &vector, "acceptable result policy");
+ rte_free(vector.msg);
+ rte_free(sig);
+ continue;
+ }
+ if ((sig_len & 1) != 0 || sig_len == 0) {
+ debug_vector("SKIP", name, &vector, "malformed P1363 signature");
+ stats->skipped_unsupported++;
+ rte_free(vector.msg);
+ rte_free(sig);
+ continue;
+ }
+ ret = run_dsa_verify(&dsa, vector.msg, vector.msg_len, sig, sig_len / 2,
+ sig + sig_len / 2, sig_len / 2, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ } else if ((strcmp(vector.result, "valid") == 0 &&
+ (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS)) ||
+ (strcmp(vector.result, "invalid") == 0 &&
+ (ret != 0 || status == RTE_CRYPTO_OP_STATUS_SUCCESS))) {
+ debug_vector("FAIL", name, &vector, "unexpected verification result");
+ stats->failed++;
+ } else if (strcmp(vector.result, "valid") == 0 ||
+ strcmp(vector.result, "invalid") == 0) {
+ stats->passed++;
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "unknown result");
+ }
+ rte_free(vector.msg);
+ rte_free(sig);
+ }
+ rte_free(dsa.p);
+ rte_free(dsa.q);
+ rte_free(dsa.g);
+ rte_free(dsa.y);
+ }
+ return 0;
+}
+
+static int
+map_curve(const char *name, enum rte_crypto_curve_id *curve, uint32_t *bytesize)
+{
+ if (strcmp(name, "secp224r1") == 0) {
+ *curve = RTE_CRYPTO_EC_GROUP_SECP224R1;
+ *bytesize = 28;
+ } else if (strcmp(name, "secp256r1") == 0) {
+ *curve = RTE_CRYPTO_EC_GROUP_SECP256R1;
+ *bytesize = 32;
+ } else if (strcmp(name, "secp384r1") == 0) {
+ *curve = RTE_CRYPTO_EC_GROUP_SECP384R1;
+ *bytesize = 48;
+ } else if (strcmp(name, "secp521r1") == 0) {
+ *curve = RTE_CRYPTO_EC_GROUP_SECP521R1;
+ *bytesize = 66;
+ } else {
+ return -ENOTSUP;
+ }
+ return 0;
+}
+
+/* Left-align an unsigned big-endian value into a fixed-width field. */
+static bool
+value_equals_padded(const uint8_t *a, uint32_t a_len, const uint8_t *b, uint32_t b_len)
+{
+ while (a_len > 0 && a[0] == 0) {
+ a++;
+ a_len--;
+ }
+ while (b_len > 0 && b[0] == 0) {
+ b++;
+ b_len--;
+ }
+ return a_len == b_len && memcmp(a, b, a_len) == 0;
+}
+
+/* Left-pad a big-endian value into a fixed-width field: strip leading zeros,
+ * reject values wider than the field, zero-pad the remaining prefix.
+ */
+static int
+normalize_operand(const uint8_t *value, uint32_t value_len, uint8_t *out, uint32_t bytesize)
+{
+ while (value_len > 0 && value[0] == 0) {
+ value++;
+ value_len--;
+ }
+ if (value_len > bytesize)
+ return -EINVAL;
+ memset(out, 0, bytesize);
+ memcpy(out + bytesize - value_len, value, value_len);
+ return 0;
+}
+
+static int
+run_ecdh_ecpoint(enum rte_crypto_curve_id curve, uint32_t bytesize,
+ const uint8_t *priv, uint32_t priv_len, const uint8_t *pub_x,
+ const uint8_t *pub_y, uint8_t **shared_x, uint32_t *shared_len,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_asym_xform xform = { 0 };
+ void *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_cryptodev_info dev_info;
+ struct rte_cryptodev_asym_capability_idx index = {
+ .type = RTE_CRYPTO_ASYM_XFORM_ECDH,
+ };
+ const struct rte_cryptodev_asymmetric_xform_capability *capability;
+ uint8_t *out_x = NULL;
+ uint8_t *out_y = NULL;
+ uint8_t priv_buf[MAX_EC_BYTESIZE];
+ int ret;
+
+ *shared_x = NULL;
+ if (env.asym_session_pool == NULL || env.asym_op_pool == NULL)
+ return -ENOTSUP;
+ rte_cryptodev_info_get(env.dev_id, &dev_info);
+ if ((dev_info.feature_flags & RTE_CRYPTODEV_FF_ASYMMETRIC_CRYPTO) == 0)
+ return -ENOTSUP;
+ capability = rte_cryptodev_asym_capability_get(env.dev_id, &index);
+ if (capability == NULL)
+ return -ENOTSUP;
+ if (rte_cryptodev_asym_xform_capability_check_optype(capability,
+ (enum rte_crypto_asym_op_type)
+ RTE_CRYPTO_ASYM_KE_SHARED_SECRET_COMPUTE) == 0)
+ return -ENOTSUP;
+
+ if (bytesize > sizeof(priv_buf))
+ return -ENOTSUP;
+ if (normalize_operand(priv, priv_len, priv_buf, bytesize) != 0)
+ return -EINVAL;
+
+ xform.xform_type = RTE_CRYPTO_ASYM_XFORM_ECDH;
+ xform.next = NULL;
+ xform.ec.curve_id = curve;
+ xform.ec.pkey.data = priv_buf;
+ xform.ec.pkey.length = bytesize;
+ xform.ec.q.x.data = (uint8_t *)(uintptr_t)pub_x;
+ xform.ec.q.x.length = bytesize;
+ xform.ec.q.y.data = (uint8_t *)(uintptr_t)pub_y;
+ xform.ec.q.y.length = bytesize;
+
+ ret = -ENOMEM;
+ out_x = rte_zmalloc(NULL, bytesize, 0);
+ out_y = rte_zmalloc(NULL, bytesize, 0);
+ if (out_x == NULL || out_y == NULL)
+ goto out;
+ /* Session-create failure after the capability check is a PMD failure. */
+ if (rte_cryptodev_asym_session_create(env.dev_id, &xform, env.asym_session_pool,
+ &session) < 0 || session == NULL) {
+ ret = -EIO;
+ goto out;
+ }
+ op = rte_crypto_op_alloc(env.asym_op_pool, RTE_CRYPTO_OP_TYPE_ASYMMETRIC);
+ if (op == NULL)
+ goto out;
+ op->asym->ecdh.ke_type = RTE_CRYPTO_ASYM_KE_SHARED_SECRET_COMPUTE;
+ op->asym->ecdh.priv_key.data = priv_buf;
+ op->asym->ecdh.priv_key.length = bytesize;
+ op->asym->ecdh.pub_key.x.data = (uint8_t *)(uintptr_t)pub_x;
+ op->asym->ecdh.pub_key.x.length = bytesize;
+ op->asym->ecdh.pub_key.y.data = (uint8_t *)(uintptr_t)pub_y;
+ op->asym->ecdh.pub_key.y.length = bytesize;
+ op->asym->ecdh.shared_secret.x.data = out_x;
+ op->asym->ecdh.shared_secret.x.length = bytesize;
+ op->asym->ecdh.shared_secret.y.data = out_y;
+ op->asym->ecdh.shared_secret.y.length = bytesize;
+ rte_crypto_op_attach_asym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL) {
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+ *status = completed->status;
+ if (completed->status == RTE_CRYPTO_OP_STATUS_SUCCESS) {
+ *shared_x = out_x;
+ *shared_len = completed->asym->ecdh.shared_secret.x.length;
+ out_x = NULL;
+ }
+ ret = 0;
+
+out:
+ rte_free(out_x);
+ rte_free(out_y);
+ rte_crypto_op_free(op);
+ if (session != NULL)
+ rte_cryptodev_asym_session_free(env.dev_id, session);
+ return ret;
+}
+
+static int
+process_ecdh_ecpoint(json_t *root, const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *tests = json_object_get(group, "tests");
+ enum rte_crypto_curve_id curve;
+ uint32_t bytesize;
+ const char *curve_name;
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_array(tests))
+ return -EINVAL;
+ curve_name = json_string_value(json_object_get(group, "curve"));
+ if (curve_name == NULL || map_curve(curve_name, &curve, &bytesize) != 0) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *pub = NULL;
+ uint8_t *priv = NULL;
+ uint8_t *shared = NULL;
+ uint8_t *result_x = NULL;
+ uint32_t pub_len = 0, priv_len = 0, shared_len = 0, result_len = 0;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ bool matched;
+ json_t *tc_id;
+ int ret;
+
+ memset(&vector, 0, sizeof(vector));
+ tc_id = json_object_get(test, "tcId");
+ vector.result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector.result == NULL) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ vector.tc_id = json_integer_value(tc_id);
+ if (decode_hex(json_object_get(test, "public"), &pub, &pub_len) != 0 ||
+ decode_hex(json_object_get(test, "private"), &priv,
+ &priv_len) != 0 ||
+ decode_hex(json_object_get(test, "shared"), &shared,
+ &shared_len) != 0) {
+ rte_free(pub);
+ rte_free(priv);
+ rte_free(shared);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, &vector, "acceptable result policy");
+ goto next;
+ }
+ /* Only uncompressed points (0x04 || x || y) map to the DPDK EC point. */
+ if (pub_len != 1 + 2 * bytesize || pub[0] != 0x04) {
+ debug_vector("SKIP", name, &vector, "non-uncompressed public point");
+ stats->skipped_unsupported++;
+ goto next;
+ }
+ /* Invalid-curve rejection cases carry no expected shared secret;
+ * on-curve validation is a separate ECDH PUB_KEY_VERIFY op, not
+ * part of the raw shared-secret compute primitive.
+ */
+ if (shared_len == 0) {
+ debug_vector("SKIP", name, &vector,
+ "no expected shared secret (point validation out of scope)");
+ stats->skipped_unsupported++;
+ goto next;
+ }
+ ret = run_ecdh_ecpoint(curve, bytesize, priv, priv_len, pub + 1,
+ pub + 1 + bytesize, &result_x, &result_len, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ goto next;
+ }
+ if (ret == -EINVAL) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "operand wider than curve");
+ goto next;
+ }
+ matched = ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS &&
+ value_equals_padded(result_x, result_len, shared, shared_len);
+ if (strcmp(vector.result, "valid") == 0) {
+ if (ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS && matched) {
+ stats->passed++;
+ } else if (ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS) {
+ debug_vector("FAIL", name, &vector, "wrong shared secret");
+ stats->failed++;
+ } else {
+ /* PMD returned an error for a valid vector. */
+ debug_vector("FAIL", name, &vector, "PMD could not compute");
+ stats->failed++;
+ }
+ } else if (strcmp(vector.result, "invalid") == 0) {
+ if (matched) {
+ debug_vector("FAIL", name, &vector, "produced forbidden secret");
+ stats->failed++;
+ } else {
+ stats->passed++;
+ }
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "unknown result");
+ }
+next:
+ rte_free(result_x);
+ rte_free(pub);
+ rte_free(priv);
+ rte_free(shared);
+ }
+ }
+ return 0;
+}
+
+struct ecdsa_group {
+ uint8_t *wx;
+ uint8_t *wy;
+ uint32_t wx_len;
+ uint32_t wy_len;
+ enum rte_crypto_curve_id curve;
+ uint32_t bytesize;
+ enum rte_crypto_auth_algorithm hash;
+ uint32_t digest_len;
+};
+
+static int
+run_ecdsa_verify(const struct ecdsa_group *group, const uint8_t *msg, uint32_t msg_len,
+ const uint8_t *r, uint32_t r_len, const uint8_t *s, uint32_t s_len,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_asym_xform xform = { 0 };
+ void *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_cryptodev_info dev_info;
+ struct rte_cryptodev_asym_capability_idx index = {
+ .type = RTE_CRYPTO_ASYM_XFORM_ECDSA,
+ };
+ const struct rte_cryptodev_asymmetric_xform_capability *capability;
+ uint8_t digest[64];
+ uint8_t wx_buf[MAX_EC_BYTESIZE];
+ uint8_t wy_buf[MAX_EC_BYTESIZE];
+ uint32_t e_len;
+ int ret;
+
+ if (env.asym_session_pool == NULL || env.asym_op_pool == NULL)
+ return -ENOTSUP;
+ rte_cryptodev_info_get(env.dev_id, &dev_info);
+ if ((dev_info.feature_flags & RTE_CRYPTODEV_FF_ASYMMETRIC_CRYPTO) == 0)
+ return -ENOTSUP;
+ capability = rte_cryptodev_asym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_asym_xform_capability_check_optype(capability,
+ RTE_CRYPTO_ASYM_OP_VERIFY) == 0)
+ return -ENOTSUP;
+
+ ret = compute_hash(msg, msg_len, group->hash, digest, group->digest_len);
+ if (ret != 0)
+ return ret;
+ /* ECDSA uses e = leftmost Ln bits of the hash (byte-aligned truncation). */
+ e_len = group->digest_len > group->bytesize ? group->bytesize : group->digest_len;
+
+ if (group->bytesize > sizeof(wx_buf))
+ return -ENOTSUP;
+ if (normalize_operand(group->wx, group->wx_len, wx_buf, group->bytesize) != 0 ||
+ normalize_operand(group->wy, group->wy_len, wy_buf, group->bytesize) != 0)
+ return -EINVAL;
+
+ xform.xform_type = RTE_CRYPTO_ASYM_XFORM_ECDSA;
+ xform.next = NULL;
+ xform.ec.curve_id = group->curve;
+ xform.ec.q.x.data = wx_buf;
+ xform.ec.q.x.length = group->bytesize;
+ xform.ec.q.y.data = wy_buf;
+ xform.ec.q.y.length = group->bytesize;
+
+ ret = -ENOMEM;
+ /* Session-create failure after the capability check is a PMD failure. */
+ if (rte_cryptodev_asym_session_create(env.dev_id, &xform, env.asym_session_pool,
+ &session) < 0 || session == NULL) {
+ ret = -EIO;
+ goto out;
+ }
+ op = rte_crypto_op_alloc(env.asym_op_pool, RTE_CRYPTO_OP_TYPE_ASYMMETRIC);
+ if (op == NULL)
+ goto out;
+ op->asym->ecdsa.op_type = RTE_CRYPTO_ASYM_OP_VERIFY;
+ op->asym->ecdsa.message.data = digest;
+ op->asym->ecdsa.message.length = e_len;
+ op->asym->ecdsa.r.data = (uint8_t *)(uintptr_t)r;
+ op->asym->ecdsa.r.length = r_len;
+ op->asym->ecdsa.s.data = (uint8_t *)(uintptr_t)s;
+ op->asym->ecdsa.s.length = s_len;
+ rte_crypto_op_attach_asym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL) {
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+ *status = completed->status;
+ ret = 0;
+
+out:
+ rte_crypto_op_free(op);
+ if (session != NULL)
+ rte_cryptodev_asym_session_free(env.dev_id, session);
+ return ret;
+}
+
+static int
+process_ecdsa_p1363(json_t *root, const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *pub = json_object_get(group, "publicKey");
+ json_t *tests = json_object_get(group, "tests");
+ struct ecdsa_group ec = { 0 };
+ const char *sha;
+ const char *curve_name;
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_object(pub) || !json_is_array(tests))
+ return -EINVAL;
+ sha = json_string_value(json_object_get(group, "sha"));
+ curve_name = json_string_value(json_object_get(pub, "curve"));
+ if (sha == NULL || curve_name == NULL ||
+ map_sha(sha, &ec.hash, &ec.digest_len) != 0 ||
+ map_curve(curve_name, &ec.curve, &ec.bytesize) != 0) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (decode_hex(json_object_get(pub, "wx"), &ec.wx, &ec.wx_len) != 0 ||
+ decode_hex(json_object_get(pub, "wy"), &ec.wy, &ec.wy_len) != 0) {
+ rte_free(ec.wx);
+ rte_free(ec.wy);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *sig = NULL;
+ uint32_t sig_len = 0;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ json_t *tc_id;
+ int ret;
+
+ memset(&vector, 0, sizeof(vector));
+ tc_id = json_object_get(test, "tcId");
+ vector.result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector.result == NULL) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ vector.tc_id = json_integer_value(tc_id);
+ if (decode_hex(json_object_get(test, "msg"), &vector.msg,
+ &vector.msg_len) != 0 ||
+ decode_hex(json_object_get(test, "sig"),
+ &sig, &sig_len) != 0) {
+ rte_free(vector.msg);
+ rte_free(sig);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, &vector, "acceptable result policy");
+ rte_free(vector.msg);
+ rte_free(sig);
+ continue;
+ }
+ if (sig_len != 2 * ec.bytesize) {
+ debug_vector("SKIP", name, &vector,
+ "non-canonical P1363 signature size");
+ stats->skipped_unsupported++;
+ rte_free(vector.msg);
+ rte_free(sig);
+ continue;
+ }
+ ret = run_ecdsa_verify(&ec, vector.msg, vector.msg_len, sig, sig_len / 2,
+ sig + sig_len / 2, sig_len / 2, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ } else if (ret == -EINVAL) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "operand wider than curve");
+ } else if (strcmp(vector.result, "valid") == 0) {
+ if (ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS)
+ stats->passed++;
+ else {
+ debug_vector("FAIL", name, &vector, "valid signature rejected");
+ stats->failed++;
+ }
+ } else if (strcmp(vector.result, "invalid") == 0) {
+ if (ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS) {
+ debug_vector("FAIL", name, &vector, "invalid signature accepted");
+ stats->failed++;
+ } else {
+ stats->passed++;
+ }
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "unknown result");
+ }
+ rte_free(vector.msg);
+ rte_free(sig);
+ }
+ rte_free(ec.wx);
+ rte_free(ec.wy);
+ }
+ return 0;
+}
+
+static int
+process_file(const char *path, struct app_stats *stats)
+{
+ json_error_t error;
+ json_t *root;
+ const char *algorithm;
+ const char *schema;
+ int ret;
+
+ root = json_load_file(path, 0, &error);
+ if (root == NULL) {
+ if (env.debug)
+ printf("SKIP %s: JSON error at line %d: %s\n", path, error.line,
+ error.text);
+ return 0;
+ }
+ algorithm = json_string_value(json_object_get(root, "algorithm"));
+ schema = json_string_value(json_object_get(root, "schema"));
+ if (algorithm != NULL && schema != NULL && strcmp(algorithm, "AES-GCM") == 0 &&
+ strcmp(schema, "aead_test_schema_v1.json") == 0) {
+ printf("Processing AES-GCM vectors: %s\n", path);
+ ret = process_aead(root, RTE_CRYPTO_AEAD_AES_GCM, "AES-GCM", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "AES-CCM") == 0 &&
+ strcmp(schema, "aead_test_schema_v1.json") == 0) {
+ printf("Processing AES-CCM vectors: %s\n", path);
+ ret = process_aead(root, RTE_CRYPTO_AEAD_AES_CCM, "AES-CCM", stats);
+ } else if (algorithm != NULL && schema != NULL &&
+ strcmp(algorithm, "CHACHA20-POLY1305") == 0 &&
+ strcmp(schema, "aead_test_schema_v1.json") == 0) {
+ printf("Processing ChaCha20-Poly1305 vectors: %s\n", path);
+ ret = process_aead(root, RTE_CRYPTO_AEAD_CHACHA20_POLY1305,
+ "ChaCha20-Poly1305", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "SM4-GCM") == 0 &&
+ strcmp(schema, "aead_test_schema_v1.json") == 0) {
+ printf("Processing SM4-GCM vectors: %s\n", path);
+ ret = process_aead(root, RTE_CRYPTO_AEAD_SM4_GCM, "SM4-GCM", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "HMACSHA256") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA256 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA256_HMAC, "HMAC-SHA256", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "HMACSHA1") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA1 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA1_HMAC, "HMAC-SHA1", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "HMACSHA224") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA224 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA224_HMAC, "HMAC-SHA224", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "HMACSHA384") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA384 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA384_HMAC, "HMAC-SHA384", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "HMACSHA512") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA512 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA512_HMAC, "HMAC-SHA512", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "HMACSHA3-224") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA3-224 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA3_224_HMAC, "HMAC-SHA3-224", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "HMACSHA3-256") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA3-256 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA3_256_HMAC, "HMAC-SHA3-256", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "HMACSHA3-384") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA3-384 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA3_384_HMAC, "HMAC-SHA3-384", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "HMACSHA3-512") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA3-512 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA3_512_HMAC, "HMAC-SHA3-512", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "HMACSM3") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SM3 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SM3_HMAC, "HMAC-SM3", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "AES-CMAC") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing AES-CMAC vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_AES_CMAC, "AES-CMAC", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "AES-GMAC") == 0 &&
+ strcmp(schema, "mac_with_iv_test_schema_v1.json") == 0) {
+ printf("Processing AES-GMAC vectors: %s\n", path);
+ ret = process_gmac(root, stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "DSA") == 0 &&
+ strcmp(schema, "dsa_p1363_verify_schema_v1.json") == 0) {
+ printf("Processing DSA (P1363) vectors: %s\n", path);
+ ret = process_dsa_p1363(root, "DSA", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "ECDH") == 0 &&
+ strcmp(schema, "ecdh_ecpoint_test_schema_v1.json") == 0) {
+ printf("Processing ECDH (ecpoint) vectors: %s\n", path);
+ ret = process_ecdh_ecpoint(root, "ECDH", stats);
+ } else if (algorithm != NULL && schema != NULL && strcmp(algorithm, "ECDSA") == 0 &&
+ strcmp(schema, "ecdsa_p1363_verify_schema_v1.json") == 0) {
+ printf("Processing ECDSA (P1363) vectors: %s\n", path);
+ ret = process_ecdsa_p1363(root, "ECDSA", stats);
+ } else if (algorithm != NULL &&
+ (strncmp(algorithm, "AES-", strlen("AES-")) == 0 ||
+ strncmp(algorithm, "SEED-", strlen("SEED-")) == 0 ||
+ strcmp(algorithm, "SM4-CCM") == 0 ||
+ strncmp(algorithm, "HMAC", strlen("HMAC")) == 0 ||
+ strncmp(algorithm, "RSA", strlen("RSA")) == 0 ||
+ strcmp(algorithm, "DSA") == 0 ||
+ strcmp(algorithm, "ECDH") == 0 ||
+ strcmp(algorithm, "ECDSA") == 0)) {
+ debug_file("SKIP", path, algorithm);
+ stats->skipped_unsupported++;
+ ret = 0;
+ } else {
+ debug_file("SKIP", path, "unsupported vector file");
+ stats->skipped_unsupported++;
+ ret = 0;
+ }
+ json_decref(root);
+ return ret;
+}
+
+static int
+process_path(const char *path, struct app_stats *stats)
+{
+ struct stat path_stat;
+ DIR *directory;
+ struct dirent *entry;
+ int ret = 0;
+
+ if (stat(path, &path_stat) != 0)
+ return -errno;
+ if (!S_ISDIR(path_stat.st_mode))
+ return process_file(path, stats);
+
+ directory = opendir(path);
+ if (directory == NULL)
+ return -errno;
+ while ((entry = readdir(directory)) != NULL) {
+ char file_path[PATH_MAX];
+ size_t name_length = strlen(entry->d_name);
+
+ if (name_length < 6 || strcmp(entry->d_name + name_length - 5, ".json") != 0)
+ continue;
+ if (snprintf(file_path, sizeof(file_path), "%s/%s", path, entry->d_name) >=
+ (int)sizeof(file_path)) {
+ ret = -ENAMETOOLONG;
+ break;
+ }
+ ret = process_file(file_path, stats);
+ if (ret != 0)
+ break;
+ }
+ closedir(directory);
+ return ret;
+}
+
+int
+main(int argc, char **argv)
+{
+ struct app_stats stats = { 0 };
+ int eal_args;
+ int ret;
+
+ eal_args = rte_eal_init(argc, argv);
+ if (eal_args < 0)
+ rte_exit(EXIT_FAILURE, "Cannot initialize EAL\n");
+ argc -= eal_args;
+ argv += eal_args;
+
+ ret = parse_args(argc, argv);
+ if (ret != 0) {
+ usage(argv[0]);
+ ret = EXIT_FAILURE;
+ goto out_eal;
+ }
+ ret = app_init();
+ if (ret != 0) {
+ printf("Cannot initialize cryptodev %u: %s\n", env.dev_id, rte_strerror(-ret));
+ ret = EXIT_FAILURE;
+ goto out_eal;
+ }
+
+ ret = process_path(env.vectors_path, &stats);
+ if (ret != 0)
+ printf("Vector processing failed: %s\n", rte_strerror(-ret));
+ printf("Summary: passed=%" PRIu64 " failed=%" PRIu64
+ " skipped_capability=%" PRIu64 " skipped_acceptable=%" PRIu64
+ " skipped_unsupported=%" PRIu64 "\n", stats.passed, stats.failed,
+ stats.skipped_capability, stats.skipped_acceptable, stats.skipped_unsupported);
+ app_uninit();
+ ret = ret == 0 && stats.failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+
+out_eal:
+ rte_eal_cleanup();
+ return ret;
+}
diff --git a/examples/wycheproof_validation/meson.build b/examples/wycheproof_validation/meson.build
new file mode 100644
index 0000000000..7f18b4cef3
--- /dev/null
+++ b/examples/wycheproof_validation/meson.build
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026
+
+# meson file, for building this example as part of a main DPDK build.
+#
+# To build this example as a standalone application with an already-installed
+# DPDK instance, use 'make'
+
+deps += ['cryptodev']
+
+if not dpdk_conf.has('RTE_HAS_JANSSON')
+ build = false
+ message('Skipping wycheproof_validation: Jansson is required')
+else
+ ext_deps += jansson_dep
+ sources = files('main.c')
+endif
--
2.34.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* RE: [EXTERNAL] [PATCH v3] examples: add Wycheproof validation app
2026-09-17 15:34 ` [PATCH v3] " Kai Ji
@ 2026-09-21 12:05 ` Gowrishankar Muthukrishnan
2026-09-21 16:41 ` Stephen Hemminger
2026-09-22 14:47 ` [PATCH v4] " Kai Ji
2 siblings, 0 replies; 17+ messages in thread
From: Gowrishankar Muthukrishnan @ 2026-09-21 12:05 UTC (permalink / raw)
To: Kai Ji, dev@dpdk.org; +Cc: Thomas Monjalon
Except for few minor comments below, the patch looks good to me.
Acked-by: Gowrishankar Muthukrishnan <gmuthukrishn@marvell.com>
> Add a Wycheproof JSON vector validation example for cryptodev PMDs.
>
<CUT>
> +static int
> +app_init(void)
> +{
> + struct rte_cryptodev_config config = { rte_socket_id(), 1, 0 };
> + struct rte_cryptodev_qp_conf queue_pair = { 128, NULL };
Could this be through a macro ? and have a check for max_nb_queue_pairs from dev info.
> + uint32_t session_size;
> + int ret;
> +
> + ret = rte_cryptodev_configure(env.dev_id, &config);
> + if (ret < 0)
> + return ret;
> +
> + env.mbuf_pool =
> rte_pktmbuf_pool_create("WYCHEPROOF_MBUF_POOL", 64, 0, 0,
> + env.mbuf_data_room, rte_socket_id());
> + if (env.mbuf_pool == NULL) {
> + ret = -rte_errno;
> + goto error;
> + }
> +
<CUT>
> +
> +static int
> +run_aead(const struct wycheproof_data *vector, enum
> rte_crypto_aead_algorithm algorithm,
> + enum rte_crypto_aead_operation operation, uint8_t **output,
> uint8_t **digest,
> + enum rte_crypto_op_status *status)
> +{
> + struct rte_crypto_sym_xform xform = { 0 };
> + struct rte_cryptodev_sym_session *session = NULL;
> + struct rte_crypto_op *operation_op = NULL;
> + struct rte_mbuf *mbuf = NULL;
> + struct rte_crypto_op *completed = NULL;
> + struct rte_crypto_sym_op *sym_op;
> + uint8_t *aad = NULL;
> + uint8_t *input;
> + uint32_t input_len;
> + int ret = -ENOMEM;
> +
> + *output = NULL;
> + *digest = NULL;
> + xform.type = RTE_CRYPTO_SYM_XFORM_AEAD;
> + xform.aead.algo = algorithm;
> + xform.aead.op = operation;
> + xform.aead.key.data = vector->key;
> + xform.aead.key.length = vector->key_len;
> + xform.aead.iv.offset = IV_OFFSET;
> + xform.aead.iv.length = vector->iv_len;
> + xform.aead.aad_length = vector->aad_len;
> + xform.aead.digest_length = vector->tag_len;
> +
> + session = rte_cryptodev_sym_session_create(env.dev_id, &xform,
> env.session_pool);
> + if (session == NULL)
> + goto out;
> +
> + operation_op = rte_crypto_op_alloc(env.op_pool,
> RTE_CRYPTO_OP_TYPE_SYMMETRIC);
> + if (operation_op == NULL)
> + goto out;
> +
> + mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
> + if (mbuf == NULL)
> + goto out;
> + input = operation == RTE_CRYPTO_AEAD_OP_ENCRYPT ? vector->msg
> : vector->ct;
> + input_len = operation == RTE_CRYPTO_AEAD_OP_ENCRYPT ? vector-
> >msg_len : vector->ct_len;
> + if (input_len > rte_pktmbuf_tailroom(mbuf)) {
> + ret = -EMSGSIZE;
> + goto out;
> + }
> + if (input_len != 0)
> + memcpy(rte_pktmbuf_append(mbuf, input_len), input,
> input_len);
> +
> + *digest = rte_malloc(NULL, vector->tag_len, RTE_CACHE_LINE_SIZE);
> + if (*digest == NULL && vector->tag_len != 0)
> + goto out;
> + if (operation == RTE_CRYPTO_AEAD_OP_DECRYPT && vector->tag_len
> != 0)
> + memcpy(*digest, vector->tag, vector->tag_len);
> +
> + sym_op = operation_op->sym;
> + sym_op->m_src = mbuf;
> + sym_op->aead.data.offset = 0;
> + sym_op->aead.data.length = input_len;
> + if (algorithm == RTE_CRYPTO_AEAD_AES_CCM) {
> + aad = rte_zmalloc(NULL, RTE_ALIGN_CEIL(vector->aad_len +
> 18, 16), 0);
> + if (aad == NULL)
> + goto out;
> + if (vector->aad_len != 0)
> + memcpy(aad + 18, vector->aad, vector->aad_len);
Can 18 be set through a macro ?
> + sym_op->aead.aad.data = aad;
> + sym_op->aead.aad.phys_addr = rte_malloc_virt2iova(aad);
> + memcpy(rte_crypto_op_ctod_offset(operation_op, uint8_t *,
> IV_OFFSET) + 1,
> + vector->iv, vector->iv_len);
> + } else {
> + sym_op->aead.aad.data = vector->aad;
> + sym_op->aead.aad.phys_addr = rte_malloc_virt2iova(vector-
> >aad);
> + memcpy(rte_crypto_op_ctod_offset(operation_op, uint8_t *,
> IV_OFFSET), vector->iv,
> + vector->iv_len);
> + }
<CUT>
> +
> +static int
> +process_file(const char *path, struct app_stats *stats)
> +{
> + json_error_t error;
> + json_t *root;
> + const char *algorithm;
> + const char *schema;
> + int ret;
> +
> + root = json_load_file(path, 0, &error);
> + if (root == NULL) {
> + if (env.debug)
> + printf("SKIP %s: JSON error at line %d: %s\n", path,
> error.line,
> + error.text);
> + return 0;
> + }
> + algorithm = json_string_value(json_object_get(root, "algorithm"));
Can error check here help avoid below repeated error check for algorithm and schema ?
If !(json_is_string(json_object_get(root, "schema"))
Return ..
..
> + schema = json_string_value(json_object_get(root, "schema"));
> + if (algorithm != NULL && schema != NULL && strcmp(algorithm, "AES-
> GCM") == 0 &&
> + strcmp(schema, "aead_test_schema_v1.json") == 0) {
> + printf("Processing AES-GCM vectors: %s\n", path);
> + ret = process_aead(root, RTE_CRYPTO_AEAD_AES_GCM,
> "AES-GCM", stats);
> + } else if (algorithm != NULL && schema != NULL && strcmp(algorithm,
> "AES-CCM") == 0 &&
> + strcmp(schema, "aead_test_schema_v1.json") == 0) {
> + printf("Processing AES-CCM vectors: %s\n", path);
> + ret = process_aead(root, RTE_CRYPTO_AEAD_AES_CCM,
> "AES-CCM", stats);
<CUT>
> + } else if (algorithm != NULL && schema != NULL && strcmp(algorithm,
> "ECDSA") == 0 &&
> + strcmp(schema,
> "ecdsa_p1363_verify_schema_v1.json") == 0) {
> + printf("Processing ECDSA (P1363) vectors: %s\n", path);
> + ret = process_ecdsa_p1363(root, "ECDSA", stats);
> + } else if (algorithm != NULL &&
> + (strncmp(algorithm, "AES-", strlen("AES-")) == 0 ||
> + strncmp(algorithm, "SEED-", strlen("SEED-")) == 0 ||
> + strcmp(algorithm, "SM4-CCM") == 0 ||
> + strncmp(algorithm, "HMAC", strlen("HMAC")) == 0 ||
> + strncmp(algorithm, "RSA", strlen("RSA")) == 0 ||
> + strcmp(algorithm, "DSA") == 0 ||
> + strcmp(algorithm, "ECDH") == 0 ||
> + strcmp(algorithm, "ECDSA") == 0)) {
> + debug_file("SKIP", path, algorithm);
> + stats->skipped_unsupported++;
> + ret = 0;
> + } else {
> + debug_file("SKIP", path, "unsupported vector file");
> + stats->skipped_unsupported++;
> + ret = 0;
> + }
> + json_decref(root);
> + return ret;
> +}
> +
Regards,
Gowrishankar
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v3] examples: add Wycheproof validation app
2026-09-17 15:34 ` [PATCH v3] " Kai Ji
2026-09-21 12:05 ` [EXTERNAL] " Gowrishankar Muthukrishnan
@ 2026-09-21 16:41 ` Stephen Hemminger
2026-09-22 14:47 ` [PATCH v4] " Kai Ji
2 siblings, 0 replies; 17+ messages in thread
From: Stephen Hemminger @ 2026-09-21 16:41 UTC (permalink / raw)
To: Kai Ji; +Cc: dev, Thomas Monjalon
On Thu, 17 Sep 2026 15:34:19 +0000
Kai Ji <kai.ji@intel.com> wrote:
> Add a Wycheproof JSON vector validation example for cryptodev PMDs.
>
> Support these algorithms when advertised by the selected PMD:
> - AEAD: AES-GCM, AES-CCM, SM4-GCM, ChaCha20-Poly1305
> - MAC: AES-CMAC, AES-GMAC, HMAC SHA-1/SHA-2/SHA-3/SM3
> - Asymmetric: DSA (P1363 verify), ECDSA (P1363 verify),
> ECDH (ecpoint shared-secret compute)
>
> Validate valid vectors against generated ciphertexts, tags, plaintexts,
> digests, shared secrets, or signature verification, and require the
> expected rejection for invalid vectors. Digest inputs for DSA and ECDSA
> use the symmetric auth path, selecting a separate symmetric-capable
> device when the target device is asymmetric-only.
>
> Skip parameter combinations outside PMD capability ranges and identify
> recognized vector families without a compatible DPDK transform. A
> --debug option lists every failed or skipped vector.
>
> Add Meson and standalone build integration, with usage documentation.
>
> Signed-off-by: Kai Ji <kai.ji@intel.com>
> ---
Wycheproof validation example (v3) - review
Applied on 6bbb7b3, built with -Dwerror=true, ran against
crypto_openssl on 12 Wycheproof v1 files (GCM, CCM, ChaCha20-Poly1305,
HMAC-SHA1/256, CMAC, GMAC, DSA, ECDSA, ECDH). No validation failures
on supported vectors.
Errors
------
doc/guides/sample_app_ug/wycheproof_validation.rst:5
Title underline is 28 characters under a 29-character title:
Wycheproof Validation Example
============================
docutils reports "Title underline too short"; doc/guides/meson.build
adds -W under -Dwerror, so the doc build fails. Add one '='.
Warnings
--------
main.c:790-792 validate_aead_vector()
vector->msg_len > env.mbuf_data_room
Usable room in a fresh mbuf is data_room - RTE_PKTMBUF_HEADROOM.
A message inside that 128-byte window passes this check, run_aead()
returns -EMSGSIZE at line 536, and line 806 (ret != 0) counts it as
a validation failure, so the exit status is nonzero. Verified:
--mbuf-dataroom 160 on aes_gcm_test.json gives failed=54, all
ret=-90. run_hmac(), run_gmac() and compute_hash() have no
pre-check at all and misclassify the same way (--mbuf-dataroom 128
on hmac_sha256_test.json: failed=110). Compare against
env.mbuf_data_room - RTE_PKTMBUF_HEADROOM and map -EMSGSIZE to
skipped_unsupported in every caller.
main.c:148-151 parse_args()
if (parse_uint32(optarg, &value) != 0 ||
!rte_cryptodev_is_valid_dev(value))
env.dev_id = value;
rte_cryptodev_is_valid_dev() takes uint8_t; value is truncated
before the validity check and again on assignment. Verified:
--cryptodev-id 256 silently runs on device 0 while --cryptodev-id 1
is correctly rejected. Reject value > UINT8_MAX (or
>= RTE_CRYPTO_MAX_DEVS) before the call.
main.c:577-580 run_aead(), and the same pattern at 751, 1108, 1190,
1440, 1667
completed = dequeue_one(env.dev_id);
if (completed == NULL) {
ret = -ETIMEDOUT;
goto out;
}
On timeout the op, mbuf, digest/aad buffers and session are freed
at out: while the enqueued op is still owned by the PMD, then the
tool moves on to the next vector. A hardware PMD (QAT is named as
the target) completes into freed memory, and the next dequeue_one()
can hand back the stale op as the current vector's result. Treat
-ETIMEDOUT as fatal: propagate it to main() and stop, rather than
free and continue.
Info
----
main.c:807-808, 817, 918, 998
memcmp(output, vector->ct, vector->ct_len) != 0
When msg_len/ct_len is 0, run_aead() leaves *output NULL and
decode_hex() leaves the vector buffer NULL, so this is
memcmp(NULL, NULL, 0). Wycheproof has many empty-message vectors.
glibc declares memcmp nonnull; -fsanitize=nonnull-attribute trips
on it. Guard with len != 0 &&.
MAINTAINERS:2038
"Other Example Applications" is alphabetical; the new entry sits
between FIPS and Flow filtering. Move it after "VMDq examples".
main.c:180
struct rte_cryptodev_config config = { rte_socket_id(), 1, 0 };
Positional initializer; use .socket_id/.nb_queue_pairs/.ff_disable.
doc/guides/sample_app_ug/wycheproof_validation.rst
The documented crypto_openssl PMD advertises no ECDSA or ECDH xform
capability (rte_openssl_pmd_ops.c capability table), so with the
documented command line every ECDSA/ECDH vector lands in
skipped_capability (verified: 0 passed, 241 skipped on
ecdsa_secp256r1_sha256_p1363_test.json). Worth a sentence that
asymmetric coverage needs a PMD advertising those xforms.
Review-Result: ERROR
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v4] examples: add Wycheproof validation app
2026-09-17 15:34 ` [PATCH v3] " Kai Ji
2026-09-21 12:05 ` [EXTERNAL] " Gowrishankar Muthukrishnan
2026-09-21 16:41 ` Stephen Hemminger
@ 2026-09-22 14:47 ` Kai Ji
2026-09-22 16:05 ` [PATCH v5] " Kai Ji
2 siblings, 1 reply; 17+ messages in thread
From: Kai Ji @ 2026-09-22 14:47 UTC (permalink / raw)
To: dev; +Cc: Kai Ji, Thomas Monjalon
Add a Wycheproof JSON vector validation example for cryptodev PMDs.
Support these algorithms when advertised by the selected PMD:
- AEAD: AES-GCM, AES-CCM, SM4-GCM, ChaCha20-Poly1305
- MAC: AES-CMAC, AES-GMAC, HMAC SHA-1/SHA-2/SHA-3/SM3
- Asymmetric: DSA (P1363 verify), ECDSA (P1363 verify),
ECDH (ecpoint shared-secret compute)
Validate valid vectors against generated ciphertexts, tags, plaintexts,
digests, shared secrets, or signature verification, and require the
expected rejection for invalid vectors. Digest inputs for DSA and ECDSA
use the symmetric auth path, selecting a separate symmetric-capable
device when the target device is asymmetric-only.
Skip parameter combinations outside PMD capability ranges and identify
recognized vector families without a compatible DPDK transform. A
--debug option lists every failed or skipped vector.
Add Meson and standalone build integration, with usage documentation.
Signed-off-by: Kai Ji <kai.ji@intel.com>
---
MAINTAINERS | 5 +
doc/guides/rel_notes/release_26_11.rst | 6 +
doc/guides/sample_app_ug/index.rst | 1 +
.../sample_app_ug/wycheproof_validation.rst | 45 +
examples/meson.build | 1 +
examples/wycheproof_validation/Makefile | 40 +
examples/wycheproof_validation/main.c | 2051 +++++++++++++++++
examples/wycheproof_validation/meson.build | 17 +
8 files changed, 2166 insertions(+)
create mode 100644 doc/guides/sample_app_ug/wycheproof_validation.rst
create mode 100644 examples/wycheproof_validation/Makefile
create mode 100644 examples/wycheproof_validation/main.c
create mode 100644 examples/wycheproof_validation/meson.build
diff --git a/MAINTAINERS b/MAINTAINERS
index 8c50c52933..d33923cbb2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2106,3 +2106,8 @@ F: examples/vmdq/
F: doc/guides/sample_app_ug/vmdq_forwarding.rst
F: examples/vmdq_dcb/
F: doc/guides/sample_app_ug/vmdq_dcb_forwarding.rst
+
+Wycheproof validation example
+M: Kai Ji <kai.ji@intel.com>
+F: examples/wycheproof_validation/
+F: doc/guides/sample_app_ug/wycheproof_validation.rst
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 4b3e5d995c..0126d25a58 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -55,6 +55,12 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **Added Wycheproof validation example application.**
+
+ Added a new example application that validates a DPDK cryptodev PMD against
+ the Google Wycheproof JSON test vectors, covering AEAD, MAC, DSA, ECDH and
+ ECDSA algorithm families.
+
Removed Items
-------------
diff --git a/doc/guides/sample_app_ug/index.rst b/doc/guides/sample_app_ug/index.rst
index f12623bb66..29313fe3de 100644
--- a/doc/guides/sample_app_ug/index.rst
+++ b/doc/guides/sample_app_ug/index.rst
@@ -53,6 +53,7 @@ Sample Applications User Guides
ptpclient
ptp_tap_relay_sw
fips_validation
+ wycheproof_validation
ipsec_secgw
bbdev_app
ntb
diff --git a/doc/guides/sample_app_ug/wycheproof_validation.rst b/doc/guides/sample_app_ug/wycheproof_validation.rst
new file mode 100644
index 0000000000..8d954625ac
--- /dev/null
+++ b/doc/guides/sample_app_ug/wycheproof_validation.rst
@@ -0,0 +1,45 @@
+.. SPDX-License-Identifier: BSD-3-Clause
+ Copyright(c) 2026 Intel Corporation.
+
+Wycheproof Validation Example
+=============================
+
+Overview
+--------
+
+This example validates a DPDK cryptodev implementation against the Google
+Wycheproof JSON test vectors.
+
+The application reads one JSON file or a directory of JSON files at runtime and
+checks the supported algorithm families against the selected PMD. It can be used
+with a PMD that advertises AEAD, MAC, DSA, ECDH, or ECDSA support.
+
+Build
+-----
+
+Build the example from the DPDK tree with Meson:
+
+.. code-block:: console
+
+ meson setup build -Dexamples=wycheproof_validation -Denable_drivers=crypto/openssl
+ meson compile -C build
+
+Standalone Makefile builds are also supported from the example directory.
+
+Run
+---
+
+Run the example with an OpenSSL-backed cryptodev and a vector file:
+
+.. code-block:: console
+
+ ./build/examples/dpdk-wycheproof_validation --vdev crypto_openssl -- \
+ --vectors ../wycheproof/testvectors_v1/aes_gcm_test.json \
+ --cryptodev crypto_openssl --debug
+
+The program accepts a single JSON file or a directory of JSON files. Use
+``--debug`` to print each failed or skipped vector with its reason.
+
+The exit code is nonzero when a parsed and supported vector fails validation;
+unsupported vectors, capability-skipped cases, and acceptable results are reported
+in the summary without forcing a failure.
diff --git a/examples/meson.build b/examples/meson.build
index 25d9c88457..000d05863a 100644
--- a/examples/meson.build
+++ b/examples/meson.build
@@ -59,6 +59,7 @@ all_examples = [
'vm_power_manager/guest_cli',
'vmdq',
'vmdq_dcb',
+ 'wycheproof_validation',
]
# on install, skip copying all meson.build files
diff --git a/examples/wycheproof_validation/Makefile b/examples/wycheproof_validation/Makefile
new file mode 100644
index 0000000000..8cc0293364
--- /dev/null
+++ b/examples/wycheproof_validation/Makefile
@@ -0,0 +1,40 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026
+
+APP = wycheproof_validation
+SRCS-y := main.c
+
+PKGCONF ?= pkg-config
+
+ifneq ($(shell $(PKGCONF) --exists libdpdk && echo 0),0)
+$(error "no installation of DPDK found")
+endif
+ifneq ($(shell $(PKGCONF) --exists jansson && echo 0),0)
+$(error "Jansson is required")
+endif
+
+all: shared
+.PHONY: shared static
+shared: build/$(APP)-shared
+ ln -sf $(APP)-shared build/$(APP)
+static: build/$(APP)-static
+ ln -sf $(APP)-static build/$(APP)
+
+PC_FILE := $(shell $(PKGCONF) --path libdpdk 2>/dev/null)
+CFLAGS += -O3 $(shell $(PKGCONF) --cflags libdpdk jansson)
+LDFLAGS_SHARED = $(shell $(PKGCONF) --libs libdpdk jansson)
+LDFLAGS_STATIC = $(shell $(PKGCONF) --static --libs libdpdk jansson)
+
+build/$(APP)-shared: $(SRCS-y) Makefile $(PC_FILE) | build
+ $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_SHARED)
+
+build/$(APP)-static: $(SRCS-y) Makefile $(PC_FILE) | build
+ $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_STATIC)
+
+build:
+ @mkdir -p $@
+
+.PHONY: clean
+clean:
+ rm -f build/$(APP) build/$(APP)-static build/$(APP)-shared
+ test -d build && rmdir -p build || true
diff --git a/examples/wycheproof_validation/main.c b/examples/wycheproof_validation/main.c
new file mode 100644
index 0000000000..7c4c0139ae
--- /dev/null
+++ b/examples/wycheproof_validation/main.c
@@ -0,0 +1,2051 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026
+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <jansson.h>
+
+#include <rte_cryptodev.h>
+#include <rte_cycles.h>
+#include <rte_eal.h>
+#include <rte_errno.h>
+#include <rte_mbuf.h>
+#include <rte_mempool.h>
+#include <rte_malloc.h>
+
+#define APP_NAME "wycheproof_validation"
+#define DEFAULT_MBUF_DATA_ROOM 16384
+#define NUM_QP_DESCRIPTORS 128
+#define MAX_IV_LEN 512
+#define MAX_EC_BYTESIZE 66
+/* CCM AAD is prefixed with an 18-byte reserved area (B0 block + length). */
+#define AES_CCM_AAD_OFFSET 18
+#define IV_OFFSET (sizeof(struct rte_crypto_op) + sizeof(struct rte_crypto_sym_op))
+
+struct wycheproof_data {
+ uint8_t *key;
+ uint8_t *iv;
+ uint8_t *aad;
+ uint8_t *msg;
+ uint8_t *ct;
+ uint8_t *tag;
+ uint32_t key_len;
+ uint32_t iv_len;
+ uint32_t aad_len;
+ uint32_t msg_len;
+ uint32_t ct_len;
+ uint32_t tag_len;
+ json_int_t tc_id;
+ const char *result;
+};
+
+struct app_env {
+ uint8_t dev_id;
+ uint8_t hash_dev_id;
+ bool hash_dev_own;
+ uint32_t mbuf_data_room;
+ const char *vectors_path;
+ bool debug;
+ struct rte_mempool *mbuf_pool;
+ struct rte_mempool *session_pool;
+ struct rte_mempool *op_pool;
+ struct rte_mempool *asym_session_pool;
+ struct rte_mempool *asym_op_pool;
+};
+
+struct app_stats {
+ uint64_t passed;
+ uint64_t failed;
+ uint64_t skipped_capability;
+ uint64_t skipped_acceptable;
+ uint64_t skipped_unsupported;
+};
+
+static struct app_env env = {
+ .mbuf_data_room = DEFAULT_MBUF_DATA_ROOM,
+};
+
+static void
+usage(const char *program)
+{
+ printf("%s [EAL options] -- --vectors PATH [--cryptodev NAME | --cryptodev-id ID] [--debug]\n",
+ program);
+}
+
+static void
+debug_vector(const char *type, const char *algorithm,
+ const struct wycheproof_data *vector, const char *reason)
+{
+ if (env.debug)
+ printf("%s %s tcId=%" JSON_INTEGER_FORMAT " result=%s: %s\n", type,
+ algorithm, vector->tc_id, vector->result, reason);
+}
+
+static void
+debug_file(const char *type, const char *path, const char *reason)
+{
+ if (env.debug)
+ printf("%s %s: %s\n", type, path, reason);
+}
+
+static int
+parse_uint32(const char *text, uint32_t *value)
+{
+ char *end;
+ unsigned long parsed;
+
+ errno = 0;
+ parsed = strtoul(text, &end, 10);
+ if (errno != 0 || *text == '\0' || *end != '\0' || parsed > UINT32_MAX)
+ return -EINVAL;
+
+ *value = parsed;
+ return 0;
+}
+
+static int
+parse_args(int argc, char **argv)
+{
+ static const struct option options[] = {
+ { "vectors", required_argument, NULL, 'v' },
+ { "cryptodev", required_argument, NULL, 'c' },
+ { "cryptodev-id", required_argument, NULL, 'd' },
+ { "mbuf-dataroom", required_argument, NULL, 'm' },
+ { "debug", no_argument, NULL, 'D' },
+ { NULL, 0, NULL, 0 },
+ };
+ bool have_device = false;
+ int option;
+
+ if (rte_cryptodev_count() == 0)
+ return -ENODEV;
+ env.dev_id = 0;
+
+ while ((option = getopt_long(argc, argv, "v:c:d:m:D", options, NULL)) != -1) {
+ uint32_t value;
+ int dev_id;
+
+ switch (option) {
+ case 'v':
+ env.vectors_path = optarg;
+ break;
+ case 'c':
+ dev_id = rte_cryptodev_get_dev_id(optarg);
+ if (dev_id < 0)
+ return dev_id;
+ env.dev_id = dev_id;
+ have_device = true;
+ break;
+ case 'd':
+ if (parse_uint32(optarg, &value) != 0 || value > UINT8_MAX ||
+ !rte_cryptodev_is_valid_dev(value))
+ return -EINVAL;
+ env.dev_id = value;
+ have_device = true;
+ break;
+ case 'm':
+ if (parse_uint32(optarg, &env.mbuf_data_room) != 0 ||
+ env.mbuf_data_room < RTE_PKTMBUF_HEADROOM ||
+ env.mbuf_data_room > UINT16_MAX)
+ return -EINVAL;
+ break;
+ case 'D':
+ env.debug = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ if (env.vectors_path == NULL)
+ return -EINVAL;
+ if (!have_device)
+ printf("Using cryptodev %u (%s)\n", env.dev_id,
+ rte_cryptodev_name_get(env.dev_id));
+
+ return 0;
+}
+
+static int
+app_init(void)
+{
+ struct rte_cryptodev_config config = { rte_socket_id(), 1, 0 };
+ struct rte_cryptodev_qp_conf queue_pair = { NUM_QP_DESCRIPTORS, NULL };
+ struct rte_cryptodev_info dev_info;
+ uint32_t session_size;
+ int ret;
+
+ rte_cryptodev_info_get(env.dev_id, &dev_info);
+ if (dev_info.max_nb_queue_pairs < config.nb_queue_pairs)
+ return -ENOTSUP;
+
+ ret = rte_cryptodev_configure(env.dev_id, &config);
+ if (ret < 0)
+ return ret;
+
+ env.mbuf_pool = rte_pktmbuf_pool_create("WYCHEPROOF_MBUF_POOL", 64, 0, 0,
+ env.mbuf_data_room, rte_socket_id());
+ if (env.mbuf_pool == NULL) {
+ ret = -rte_errno;
+ goto error;
+ }
+
+ session_size = rte_cryptodev_sym_get_private_session_size(env.dev_id);
+ {
+ /* The hashing device may differ from the target device, so size the
+ * session pool for the largest sym session across all devices.
+ */
+ uint8_t d;
+
+ for (d = 0; d < rte_cryptodev_count(); d++) {
+ uint32_t s = rte_cryptodev_sym_get_private_session_size(d);
+
+ if (s > session_size)
+ session_size = s;
+ }
+ }
+ env.session_pool = rte_cryptodev_sym_session_pool_create("WYCHEPROOF_SESSION_POOL",
+ 64, session_size, 0, 0, rte_socket_id());
+ if (env.session_pool == NULL) {
+ ret = -rte_errno;
+ goto error;
+ }
+
+ /* priv_size is measured from the end of the sym op; the IV lives there. */
+ env.op_pool = rte_crypto_op_pool_create("WYCHEPROOF_OP_POOL",
+ RTE_CRYPTO_OP_TYPE_SYMMETRIC, 64, 0, MAX_IV_LEN, rte_socket_id());
+ if (env.op_pool == NULL) {
+ ret = -rte_errno;
+ goto error;
+ }
+
+ /* Asymmetric pools are best-effort; DSA/ECDSA paths gate on the
+ * device asymmetric feature flag at runtime.
+ */
+ env.asym_session_pool = rte_cryptodev_asym_session_pool_create(
+ "WYCHEPROOF_ASYM_SESS", 16, 0, 0, rte_socket_id());
+ env.asym_op_pool = rte_crypto_op_pool_create("WYCHEPROOF_ASYM_OP",
+ RTE_CRYPTO_OP_TYPE_ASYMMETRIC, 64, 0, 0, rte_socket_id());
+
+ queue_pair.mp_session = env.session_pool;
+ ret = rte_cryptodev_queue_pair_setup(env.dev_id, 0, &queue_pair,
+ rte_socket_id());
+ if (ret < 0)
+ goto error;
+
+ ret = rte_cryptodev_start(env.dev_id);
+ if (ret < 0)
+ goto error;
+
+ /* Digest computation for DSA/ECDSA needs a symmetric-auth device. The
+ * target device may be asym-only (for example QAT), so pick a separate
+ * sym-capable device for hashing when needed.
+ */
+ env.hash_dev_id = env.dev_id;
+ env.hash_dev_own = false;
+ {
+ struct rte_cryptodev_sym_capability_idx hash_idx = {
+ .type = RTE_CRYPTO_SYM_XFORM_AUTH,
+ .algo.auth = RTE_CRYPTO_AUTH_SHA256,
+ };
+ uint8_t count = rte_cryptodev_count();
+ uint8_t d;
+
+ if (rte_cryptodev_sym_capability_get(env.dev_id, &hash_idx) == NULL) {
+ for (d = 0; d < count; d++) {
+ struct rte_cryptodev_qp_conf hash_qp = { NUM_QP_DESCRIPTORS, NULL };
+
+ if (d == env.dev_id)
+ continue;
+ if (rte_cryptodev_sym_capability_get(d, &hash_idx) == NULL)
+ continue;
+ if (rte_cryptodev_configure(d, &config) < 0)
+ continue;
+ hash_qp.mp_session = env.session_pool;
+ if (rte_cryptodev_queue_pair_setup(d, 0, &hash_qp,
+ rte_socket_id()) < 0 ||
+ rte_cryptodev_start(d) < 0) {
+ rte_cryptodev_close(d);
+ continue;
+ }
+ env.hash_dev_id = d;
+ env.hash_dev_own = true;
+ break;
+ }
+ }
+ }
+
+ return 0;
+
+error:
+ rte_mempool_free(env.asym_op_pool);
+ rte_mempool_free(env.asym_session_pool);
+ rte_mempool_free(env.op_pool);
+ rte_mempool_free(env.session_pool);
+ rte_mempool_free(env.mbuf_pool);
+ env.asym_op_pool = NULL;
+ env.asym_session_pool = NULL;
+ env.op_pool = NULL;
+ env.session_pool = NULL;
+ env.mbuf_pool = NULL;
+ return ret;
+}
+
+static void
+app_uninit(void)
+{
+ if (env.hash_dev_own) {
+ rte_cryptodev_stop(env.hash_dev_id);
+ rte_cryptodev_close(env.hash_dev_id);
+ }
+ rte_cryptodev_stop(env.dev_id);
+ rte_cryptodev_close(env.dev_id);
+ rte_mempool_free(env.asym_op_pool);
+ rte_mempool_free(env.asym_session_pool);
+ rte_mempool_free(env.op_pool);
+ rte_mempool_free(env.session_pool);
+ rte_mempool_free(env.mbuf_pool);
+}
+
+static void
+free_vector(struct wycheproof_data *vector)
+{
+ rte_free(vector->key);
+ rte_free(vector->iv);
+ rte_free(vector->aad);
+ rte_free(vector->msg);
+ rte_free(vector->ct);
+ rte_free(vector->tag);
+ memset(vector, 0, sizeof(*vector));
+}
+
+static int
+hex_value(char character)
+{
+ if (character >= '0' && character <= '9')
+ return character - '0';
+ if (character >= 'a' && character <= 'f')
+ return character - 'a' + 10;
+ if (character >= 'A' && character <= 'F')
+ return character - 'A' + 10;
+ return -1;
+}
+
+static int
+decode_hex(json_t *value, uint8_t **buffer, uint32_t *length)
+{
+ const char *hex;
+ size_t hex_length;
+ uint8_t *decoded;
+ size_t index;
+
+ if (!json_is_string(value))
+ return -EINVAL;
+
+ hex = json_string_value(value);
+ hex_length = strlen(hex);
+ if ((hex_length & 1) != 0 || hex_length / 2 > UINT32_MAX)
+ return -EINVAL;
+
+ *length = hex_length / 2;
+ if (*length == 0)
+ return 0;
+
+ decoded = rte_malloc(NULL, *length, 0);
+ if (decoded == NULL)
+ return -ENOMEM;
+
+ for (index = 0; index < *length; index++) {
+ int high = hex_value(hex[index * 2]);
+ int low = hex_value(hex[index * 2 + 1]);
+
+ if (high < 0 || low < 0) {
+ rte_free(decoded);
+ return -EINVAL;
+ }
+ decoded[index] = (high << 4) | low;
+ }
+
+ *buffer = decoded;
+ return 0;
+}
+
+static int
+parse_vector(json_t *test, struct wycheproof_data *vector)
+{
+ json_t *tc_id;
+ int ret;
+
+ memset(vector, 0, sizeof(*vector));
+ tc_id = json_object_get(test, "tcId");
+ vector->result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector->result == NULL)
+ return -EINVAL;
+ vector->tc_id = json_integer_value(tc_id);
+
+ ret = decode_hex(json_object_get(test, "key"), &vector->key, &vector->key_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "iv"), &vector->iv, &vector->iv_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "aad"), &vector->aad, &vector->aad_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "msg"), &vector->msg, &vector->msg_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "ct"), &vector->ct, &vector->ct_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "tag"), &vector->tag, &vector->tag_len);
+ if (ret != 0)
+ goto error;
+
+ return 0;
+
+error:
+ free_vector(vector);
+ return ret;
+}
+
+static int
+parse_mac_vector(json_t *test, struct wycheproof_data *vector)
+{
+ json_t *tc_id;
+ int ret;
+
+ memset(vector, 0, sizeof(*vector));
+ tc_id = json_object_get(test, "tcId");
+ vector->result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector->result == NULL)
+ return -EINVAL;
+ vector->tc_id = json_integer_value(tc_id);
+
+ ret = decode_hex(json_object_get(test, "key"), &vector->key, &vector->key_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "msg"), &vector->msg, &vector->msg_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "tag"), &vector->tag, &vector->tag_len);
+ if (ret != 0)
+ goto error;
+
+ return 0;
+
+error:
+ free_vector(vector);
+ return ret;
+}
+
+static int
+parse_mac_with_iv_vector(json_t *test, struct wycheproof_data *vector)
+{
+ int ret;
+
+ ret = parse_mac_vector(test, vector);
+ if (ret != 0)
+ return ret;
+ ret = decode_hex(json_object_get(test, "iv"), &vector->iv, &vector->iv_len);
+ if (ret != 0)
+ free_vector(vector);
+ return ret;
+}
+
+/* Bounded wait for one completed op; NULL on timeout so a stuck PMD fails the
+ * vector instead of hanging the tool.
+ */
+static struct rte_crypto_op *
+dequeue_one(uint8_t dev_id)
+{
+ struct rte_crypto_op *completed = NULL;
+ uint64_t deadline = rte_get_timer_cycles() + rte_get_timer_hz();
+
+ while (rte_cryptodev_dequeue_burst(dev_id, 0, &completed, 1) == 0) {
+ if (rte_get_timer_cycles() > deadline)
+ return NULL;
+ rte_pause();
+ }
+ return completed;
+}
+
+static int
+check_aead_capability(const struct wycheproof_data *vector,
+ enum rte_crypto_aead_algorithm algorithm)
+{
+ struct rte_cryptodev_sym_capability_idx index = {
+ .type = RTE_CRYPTO_SYM_XFORM_AEAD,
+ .algo.aead = algorithm,
+ };
+ const struct rte_cryptodev_symmetric_capability *capability;
+
+ capability = rte_cryptodev_sym_capability_get(env.dev_id, &index);
+ if (capability == NULL)
+ return -ENOTSUP;
+
+ return rte_cryptodev_sym_capability_check_aead(capability, vector->key_len,
+ vector->tag_len, vector->aad_len, vector->iv_len);
+}
+
+static int
+run_aead(const struct wycheproof_data *vector, enum rte_crypto_aead_algorithm algorithm,
+ enum rte_crypto_aead_operation operation, uint8_t **output, uint8_t **digest,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_sym_xform xform = { 0 };
+ struct rte_cryptodev_sym_session *session = NULL;
+ struct rte_crypto_op *operation_op = NULL;
+ struct rte_mbuf *mbuf = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_crypto_sym_op *sym_op;
+ uint8_t *aad = NULL;
+ uint8_t *input;
+ uint32_t input_len;
+ int ret = -ENOMEM;
+
+ *output = NULL;
+ *digest = NULL;
+ xform.type = RTE_CRYPTO_SYM_XFORM_AEAD;
+ xform.aead.algo = algorithm;
+ xform.aead.op = operation;
+ xform.aead.key.data = vector->key;
+ xform.aead.key.length = vector->key_len;
+ xform.aead.iv.offset = IV_OFFSET;
+ xform.aead.iv.length = vector->iv_len;
+ xform.aead.aad_length = vector->aad_len;
+ xform.aead.digest_length = vector->tag_len;
+
+ session = rte_cryptodev_sym_session_create(env.dev_id, &xform, env.session_pool);
+ if (session == NULL)
+ goto out;
+
+ operation_op = rte_crypto_op_alloc(env.op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC);
+ if (operation_op == NULL)
+ goto out;
+
+ mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
+ if (mbuf == NULL)
+ goto out;
+ input = operation == RTE_CRYPTO_AEAD_OP_ENCRYPT ? vector->msg : vector->ct;
+ input_len = operation == RTE_CRYPTO_AEAD_OP_ENCRYPT ? vector->msg_len : vector->ct_len;
+ if (input_len > rte_pktmbuf_tailroom(mbuf)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ if (input_len != 0)
+ memcpy(rte_pktmbuf_append(mbuf, input_len), input, input_len);
+
+ *digest = rte_malloc(NULL, vector->tag_len, RTE_CACHE_LINE_SIZE);
+ if (*digest == NULL && vector->tag_len != 0)
+ goto out;
+ if (operation == RTE_CRYPTO_AEAD_OP_DECRYPT && vector->tag_len != 0)
+ memcpy(*digest, vector->tag, vector->tag_len);
+
+ sym_op = operation_op->sym;
+ sym_op->m_src = mbuf;
+ sym_op->aead.data.offset = 0;
+ sym_op->aead.data.length = input_len;
+ if (algorithm == RTE_CRYPTO_AEAD_AES_CCM) {
+ aad = rte_zmalloc(NULL,
+ RTE_ALIGN_CEIL(vector->aad_len + AES_CCM_AAD_OFFSET, 16), 0);
+ if (aad == NULL)
+ goto out;
+ if (vector->aad_len != 0)
+ memcpy(aad + AES_CCM_AAD_OFFSET, vector->aad, vector->aad_len);
+ sym_op->aead.aad.data = aad;
+ sym_op->aead.aad.phys_addr = rte_malloc_virt2iova(aad);
+ memcpy(rte_crypto_op_ctod_offset(operation_op, uint8_t *, IV_OFFSET) + 1,
+ vector->iv, vector->iv_len);
+ } else {
+ sym_op->aead.aad.data = vector->aad;
+ sym_op->aead.aad.phys_addr = rte_malloc_virt2iova(vector->aad);
+ memcpy(rte_crypto_op_ctod_offset(operation_op, uint8_t *, IV_OFFSET), vector->iv,
+ vector->iv_len);
+ }
+ sym_op->aead.digest.data = *digest;
+ sym_op->aead.digest.phys_addr = rte_malloc_virt2iova(*digest);
+ rte_crypto_op_attach_sym_session(operation_op, session);
+
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &operation_op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+
+ *status = completed->status;
+ if (*status == RTE_CRYPTO_OP_STATUS_SUCCESS && input_len != 0) {
+ *output = rte_malloc(NULL, input_len, 0);
+ if (*output == NULL)
+ goto out;
+ memcpy(*output, rte_pktmbuf_mtod(mbuf, uint8_t *), input_len);
+ }
+ ret = 0;
+
+out:
+ rte_free(aad);
+ rte_pktmbuf_free(mbuf);
+ rte_crypto_op_free(operation_op);
+ rte_cryptodev_sym_session_free(env.dev_id, session);
+ if (ret != 0) {
+ rte_free(*output);
+ rte_free(*digest);
+ *output = NULL;
+ *digest = NULL;
+ }
+ return ret;
+}
+
+static int
+run_hmac(const struct wycheproof_data *vector, enum rte_crypto_auth_algorithm algorithm,
+ enum rte_crypto_auth_operation operation, uint8_t **digest,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_sym_xform xform = { 0 };
+ struct rte_cryptodev_sym_session *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_mbuf *mbuf = NULL;
+ const struct rte_cryptodev_symmetric_capability *capability;
+ struct rte_cryptodev_sym_capability_idx index = {
+ .type = RTE_CRYPTO_SYM_XFORM_AUTH,
+ .algo.auth = algorithm,
+ };
+ int ret = -ENOMEM;
+
+ *digest = NULL;
+ capability = rte_cryptodev_sym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_sym_capability_check_auth(capability,
+ vector->key_len, vector->tag_len, 0) != 0)
+ return -ENOTSUP;
+
+ xform.type = RTE_CRYPTO_SYM_XFORM_AUTH;
+ xform.auth.algo = algorithm;
+ xform.auth.op = operation;
+ xform.auth.key.data = vector->key;
+ xform.auth.key.length = vector->key_len;
+ xform.auth.digest_length = vector->tag_len;
+ session = rte_cryptodev_sym_session_create(env.dev_id, &xform, env.session_pool);
+ if (session == NULL)
+ goto out;
+
+ op = rte_crypto_op_alloc(env.op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC);
+ if (op == NULL)
+ goto out;
+ mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
+ if (mbuf == NULL)
+ goto out;
+ if (vector->msg_len > rte_pktmbuf_tailroom(mbuf)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ if (vector->msg_len != 0)
+ memcpy(rte_pktmbuf_append(mbuf, vector->msg_len), vector->msg, vector->msg_len);
+
+ *digest = rte_malloc(NULL, vector->tag_len, RTE_CACHE_LINE_SIZE);
+ if (*digest == NULL && vector->tag_len != 0)
+ goto out;
+ if (operation == RTE_CRYPTO_AUTH_OP_VERIFY && vector->tag_len != 0)
+ memcpy(*digest, vector->tag, vector->tag_len);
+
+ op->sym->m_src = mbuf;
+ op->sym->auth.data.offset = 0;
+ op->sym->auth.data.length = vector->msg_len;
+ op->sym->auth.digest.data = *digest;
+ op->sym->auth.digest.phys_addr = rte_malloc_virt2iova(*digest);
+ rte_crypto_op_attach_sym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+ *status = completed->status;
+ ret = 0;
+
+out:
+ rte_pktmbuf_free(mbuf);
+ rte_crypto_op_free(op);
+ rte_cryptodev_sym_session_free(env.dev_id, session);
+ if (ret != 0) {
+ rte_free(*digest);
+ *digest = NULL;
+ }
+ return ret;
+}
+
+static int
+run_gmac(const struct wycheproof_data *vector, enum rte_crypto_auth_operation operation,
+ uint8_t **digest, enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_sym_xform xform = { 0 };
+ struct rte_cryptodev_sym_session *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_mbuf *mbuf = NULL;
+ const struct rte_cryptodev_symmetric_capability *capability;
+ struct rte_cryptodev_sym_capability_idx index = {
+ .type = RTE_CRYPTO_SYM_XFORM_AUTH,
+ .algo.auth = RTE_CRYPTO_AUTH_AES_GMAC,
+ };
+ int ret = -ENOMEM;
+
+ *digest = NULL;
+ capability = rte_cryptodev_sym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_sym_capability_check_auth(capability,
+ vector->key_len, vector->tag_len, vector->iv_len) != 0)
+ return -ENOTSUP;
+
+ xform.type = RTE_CRYPTO_SYM_XFORM_AUTH;
+ xform.auth.algo = RTE_CRYPTO_AUTH_AES_GMAC;
+ xform.auth.op = operation;
+ xform.auth.key.data = vector->key;
+ xform.auth.key.length = vector->key_len;
+ xform.auth.iv.offset = IV_OFFSET;
+ xform.auth.iv.length = vector->iv_len;
+ xform.auth.digest_length = vector->tag_len;
+ session = rte_cryptodev_sym_session_create(env.dev_id, &xform, env.session_pool);
+ if (session == NULL)
+ goto out;
+
+ op = rte_crypto_op_alloc(env.op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC);
+ if (op == NULL)
+ goto out;
+ mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
+ if (mbuf == NULL)
+ goto out;
+ if (vector->msg_len > rte_pktmbuf_tailroom(mbuf)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ if (vector->msg_len != 0)
+ memcpy(rte_pktmbuf_append(mbuf, vector->msg_len), vector->msg, vector->msg_len);
+
+ *digest = rte_malloc(NULL, vector->tag_len, RTE_CACHE_LINE_SIZE);
+ if (*digest == NULL && vector->tag_len != 0)
+ goto out;
+ if (operation == RTE_CRYPTO_AUTH_OP_VERIFY && vector->tag_len != 0)
+ memcpy(*digest, vector->tag, vector->tag_len);
+
+ op->sym->m_src = mbuf;
+ op->sym->auth.data.offset = 0;
+ op->sym->auth.data.length = vector->msg_len;
+ op->sym->auth.digest.data = *digest;
+ op->sym->auth.digest.phys_addr = rte_malloc_virt2iova(*digest);
+ memcpy(rte_crypto_op_ctod_offset(op, uint8_t *, IV_OFFSET), vector->iv, vector->iv_len);
+ rte_crypto_op_attach_sym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+ *status = completed->status;
+ ret = 0;
+
+out:
+ rte_pktmbuf_free(mbuf);
+ rte_crypto_op_free(op);
+ rte_cryptodev_sym_session_free(env.dev_id, session);
+ if (ret != 0) {
+ rte_free(*digest);
+ *digest = NULL;
+ }
+ return ret;
+}
+
+static int
+validate_aead_vector(const struct wycheproof_data *vector,
+ enum rte_crypto_aead_algorithm algorithm, const char *name,
+ struct app_stats *stats)
+{
+ uint8_t *output = NULL;
+ uint8_t *digest = NULL;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ int ret;
+
+ if (strcmp(vector->result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, vector, "acceptable result policy");
+ return 0;
+ }
+ if (strcmp(vector->result, "valid") != 0 && strcmp(vector->result, "invalid") != 0) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, vector, "unknown result");
+ return 0;
+ }
+ /* CCM writes the IV one byte into the reserved area (after the flags byte). */
+ if (vector->iv_len + (algorithm == RTE_CRYPTO_AEAD_AES_CCM ? 1u : 0u) > MAX_IV_LEN ||
+ vector->msg_len != vector->ct_len ||
+ vector->msg_len > env.mbuf_data_room - RTE_PKTMBUF_HEADROOM) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, vector, "unsupported message length");
+ return 0;
+ }
+ if (check_aead_capability(vector, algorithm) != 0) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, vector, "PMD capability");
+ return 0;
+ }
+
+ if (strcmp(vector->result, "valid") == 0) {
+ ret = run_aead(vector, algorithm, RTE_CRYPTO_AEAD_OP_ENCRYPT, &output, &digest,
+ &status);
+ if (ret == -ETIMEDOUT)
+ return -ETIMEDOUT;
+ if (ret == -EMSGSIZE)
+ goto unsupported;
+ if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS ||
+ memcmp(output, vector->ct, vector->ct_len) != 0 ||
+ memcmp(digest, vector->tag, vector->tag_len) != 0)
+ goto failed;
+ rte_free(output);
+ rte_free(digest);
+ output = NULL;
+ digest = NULL;
+ ret = run_aead(vector, algorithm, RTE_CRYPTO_AEAD_OP_DECRYPT, &output, &digest,
+ &status);
+ if (ret == -ETIMEDOUT)
+ return -ETIMEDOUT;
+ if (ret == -EMSGSIZE)
+ goto unsupported;
+ if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS ||
+ memcmp(output, vector->msg, vector->msg_len) != 0)
+ goto failed;
+ } else {
+ ret = run_aead(vector, algorithm, RTE_CRYPTO_AEAD_OP_DECRYPT, &output, &digest,
+ &status);
+ if (ret == -ETIMEDOUT)
+ return -ETIMEDOUT;
+ if (ret == -EMSGSIZE)
+ goto unsupported;
+ if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_AUTH_FAILED)
+ goto failed;
+ }
+
+ stats->passed++;
+ rte_free(output);
+ rte_free(digest);
+ return 0;
+
+unsupported:
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, vector, "message length exceeds mbuf");
+ rte_free(output);
+ rte_free(digest);
+ return 0;
+
+failed:
+ if (env.debug)
+ printf("FAIL %s tcId=%" JSON_INTEGER_FORMAT " result=%s status=%d ret=%d\n",
+ name, vector->tc_id, vector->result, status, ret);
+ stats->failed++;
+ rte_free(output);
+ rte_free(digest);
+ return 0;
+}
+
+static int
+process_aead(json_t *root, enum rte_crypto_aead_algorithm algorithm,
+ const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+
+ json_array_foreach(groups, group_index, group) {
+ json_t *tests = json_object_get(group, "tests");
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_array(tests))
+ return -EINVAL;
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ int ret = parse_vector(test, &vector);
+
+ if (ret != 0) {
+ if (env.debug)
+ printf("SKIP %s group=%zu test=%zu: malformed vector\n",
+ name, group_index, test_index);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ ret = validate_aead_vector(&vector, algorithm, name, stats);
+ if (ret == -ETIMEDOUT)
+ return -ETIMEDOUT;
+ free_vector(&vector);
+ }
+ }
+
+ return 0;
+}
+
+static int
+process_hmac(json_t *root, enum rte_crypto_auth_algorithm algorithm,
+ const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *tests = json_object_get(group, "tests");
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_array(tests))
+ return -EINVAL;
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *digest = NULL;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ int ret;
+
+ ret = parse_mac_vector(test, &vector);
+ if (ret != 0) {
+ if (env.debug)
+ printf("SKIP %s group=%zu test=%zu: malformed vector\n",
+ name, group_index, test_index);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, &vector, "acceptable result policy");
+ } else if (strcmp(vector.result, "valid") == 0) {
+ ret = run_hmac(&vector, algorithm, RTE_CRYPTO_AUTH_OP_GENERATE,
+ &digest, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ } else if (ret == -ETIMEDOUT) {
+ return -ETIMEDOUT;
+ } else if (ret == -EMSGSIZE) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "message length exceeds mbuf");
+ } else if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS ||
+ memcmp(digest, vector.tag, vector.tag_len) != 0) {
+ debug_vector("FAIL", name, &vector, "generated tag mismatch");
+ stats->failed++;
+ } else
+ stats->passed++;
+ } else if (strcmp(vector.result, "invalid") == 0) {
+ ret = run_hmac(&vector, algorithm, RTE_CRYPTO_AUTH_OP_VERIFY,
+ &digest, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ } else if (ret == -ETIMEDOUT) {
+ return -ETIMEDOUT;
+ } else if (ret == -EMSGSIZE) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "message length exceeds mbuf");
+ } else if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_AUTH_FAILED) {
+ debug_vector("FAIL", name, &vector, "authentication unexpectedly succeeded");
+ stats->failed++;
+ } else
+ stats->passed++;
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "unknown result");
+ }
+ rte_free(digest);
+ free_vector(&vector);
+ }
+ }
+ return 0;
+}
+
+static int
+process_gmac(json_t *root, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *tests = json_object_get(group, "tests");
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_array(tests))
+ return -EINVAL;
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *digest = NULL;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ int ret;
+
+ ret = parse_mac_with_iv_vector(test, &vector);
+ if (ret != 0) {
+ if (env.debug)
+ printf("SKIP AES-GMAC group=%zu test=%zu: malformed vector\n",
+ group_index, test_index);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (vector.iv_len > MAX_IV_LEN) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", "AES-GMAC", &vector, "IV too large");
+ rte_free(digest);
+ free_vector(&vector);
+ continue;
+ }
+ if (strcmp(vector.result, "valid") == 0)
+ ret = run_gmac(&vector, RTE_CRYPTO_AUTH_OP_GENERATE,
+ &digest, &status);
+ else if (strcmp(vector.result, "invalid") == 0)
+ ret = run_gmac(&vector, RTE_CRYPTO_AUTH_OP_VERIFY,
+ &digest, &status);
+ else {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", "AES-GMAC", &vector, "acceptable result policy");
+ ret = 0;
+ }
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", "AES-GMAC", &vector, "PMD capability");
+ } else if (ret == -ETIMEDOUT) {
+ return -ETIMEDOUT;
+ } else if (ret == -EMSGSIZE) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", "AES-GMAC", &vector, "message length exceeds mbuf");
+ } else if ((strcmp(vector.result, "valid") == 0 &&
+ (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS ||
+ memcmp(digest, vector.tag, vector.tag_len) != 0)) ||
+ (strcmp(vector.result, "invalid") == 0 &&
+ (ret != 0 || status != RTE_CRYPTO_OP_STATUS_AUTH_FAILED))) {
+ debug_vector("FAIL", "AES-GMAC", &vector, "unexpected operation result");
+ stats->failed++;
+ } else if (strcmp(vector.result, "valid") == 0 ||
+ strcmp(vector.result, "invalid") == 0)
+ stats->passed++;
+ rte_free(digest);
+ free_vector(&vector);
+ }
+ }
+ return 0;
+}
+
+struct dsa_group {
+ uint8_t *p;
+ uint8_t *q;
+ uint8_t *g;
+ uint8_t *y;
+ uint32_t p_len;
+ uint32_t q_len;
+ uint32_t g_len;
+ uint32_t y_len;
+ enum rte_crypto_auth_algorithm hash;
+ uint32_t digest_len;
+};
+
+static int
+map_sha(const char *name, enum rte_crypto_auth_algorithm *algo, uint32_t *digest_len)
+{
+ if (strcmp(name, "SHA-1") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA1;
+ *digest_len = 20;
+ } else if (strcmp(name, "SHA-224") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA224;
+ *digest_len = 28;
+ } else if (strcmp(name, "SHA-256") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA256;
+ *digest_len = 32;
+ } else if (strcmp(name, "SHA-384") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA384;
+ *digest_len = 48;
+ } else if (strcmp(name, "SHA-512") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA512;
+ *digest_len = 64;
+ } else {
+ return -ENOTSUP;
+ }
+ return 0;
+}
+
+/* Compute a plain (keyless) message digest through the cryptodev auth path. */
+static int
+compute_hash(const uint8_t *msg, uint32_t msg_len, enum rte_crypto_auth_algorithm algo,
+ uint8_t *digest, uint32_t digest_len)
+{
+ struct rte_crypto_sym_xform xform = { 0 };
+ struct rte_cryptodev_sym_session *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_mbuf *mbuf = NULL;
+ const struct rte_cryptodev_symmetric_capability *capability;
+ struct rte_cryptodev_sym_capability_idx index = {
+ .type = RTE_CRYPTO_SYM_XFORM_AUTH,
+ .algo.auth = algo,
+ };
+ uint8_t *op_digest = NULL;
+ int ret = -ENOMEM;
+
+ capability = rte_cryptodev_sym_capability_get(env.hash_dev_id, &index);
+ if (capability == NULL || rte_cryptodev_sym_capability_check_auth(capability,
+ 0, digest_len, 0) != 0)
+ return -ENOTSUP;
+
+ xform.type = RTE_CRYPTO_SYM_XFORM_AUTH;
+ xform.auth.algo = algo;
+ xform.auth.op = RTE_CRYPTO_AUTH_OP_GENERATE;
+ xform.auth.digest_length = digest_len;
+ session = rte_cryptodev_sym_session_create(env.hash_dev_id, &xform, env.session_pool);
+ if (session == NULL)
+ goto out;
+
+ op = rte_crypto_op_alloc(env.op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC);
+ if (op == NULL)
+ goto out;
+ mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
+ if (mbuf == NULL)
+ goto out;
+ if (msg_len > rte_pktmbuf_tailroom(mbuf)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ if (msg_len != 0)
+ memcpy(rte_pktmbuf_append(mbuf, msg_len), msg, msg_len);
+
+ op_digest = rte_malloc(NULL, digest_len, RTE_CACHE_LINE_SIZE);
+ if (op_digest == NULL)
+ goto out;
+ op->sym->m_src = mbuf;
+ op->sym->auth.data.offset = 0;
+ op->sym->auth.data.length = msg_len;
+ op->sym->auth.digest.data = op_digest;
+ op->sym->auth.digest.phys_addr = rte_malloc_virt2iova(op_digest);
+ rte_crypto_op_attach_sym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.hash_dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.hash_dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+ if (completed->status != RTE_CRYPTO_OP_STATUS_SUCCESS) {
+ ret = -EIO;
+ goto out;
+ }
+ memcpy(digest, op_digest, digest_len);
+ ret = 0;
+
+out:
+ rte_free(op_digest);
+ rte_pktmbuf_free(mbuf);
+ rte_crypto_op_free(op);
+ rte_cryptodev_sym_session_free(env.hash_dev_id, session);
+ return ret;
+}
+
+static int
+run_dsa_verify(const struct dsa_group *group, const uint8_t *msg, uint32_t msg_len,
+ const uint8_t *r, uint32_t r_len, const uint8_t *s, uint32_t s_len,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_asym_xform xform = { 0 };
+ void *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_cryptodev_info dev_info;
+ struct rte_cryptodev_asym_capability_idx index = {
+ .type = RTE_CRYPTO_ASYM_XFORM_DSA,
+ };
+ const struct rte_cryptodev_asymmetric_xform_capability *capability;
+ uint8_t digest[64];
+ int ret;
+
+ if (env.asym_session_pool == NULL || env.asym_op_pool == NULL)
+ return -ENOTSUP;
+ rte_cryptodev_info_get(env.dev_id, &dev_info);
+ if ((dev_info.feature_flags & RTE_CRYPTODEV_FF_ASYMMETRIC_CRYPTO) == 0)
+ return -ENOTSUP;
+ capability = rte_cryptodev_asym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_asym_xform_capability_check_optype(capability,
+ RTE_CRYPTO_ASYM_OP_VERIFY) == 0)
+ return -ENOTSUP;
+
+ ret = compute_hash(msg, msg_len, group->hash, digest, group->digest_len);
+ if (ret != 0)
+ return ret;
+
+ xform.xform_type = RTE_CRYPTO_ASYM_XFORM_DSA;
+ xform.next = NULL;
+ xform.dsa.p.data = group->p;
+ xform.dsa.p.length = group->p_len;
+ xform.dsa.q.data = group->q;
+ xform.dsa.q.length = group->q_len;
+ xform.dsa.g.data = group->g;
+ xform.dsa.g.length = group->g_len;
+
+ ret = -ENOMEM;
+ if (rte_cryptodev_asym_session_create(env.dev_id, &xform, env.asym_session_pool,
+ &session) < 0 || session == NULL)
+ goto out;
+ op = rte_crypto_op_alloc(env.asym_op_pool, RTE_CRYPTO_OP_TYPE_ASYMMETRIC);
+ if (op == NULL)
+ goto out;
+ op->asym->dsa.op_type = RTE_CRYPTO_ASYM_OP_VERIFY;
+ /* DSA verify operates on the message digest. */
+ op->asym->dsa.message.data = digest;
+ op->asym->dsa.message.length = group->digest_len;
+ op->asym->dsa.r.data = (uint8_t *)(uintptr_t)r;
+ op->asym->dsa.r.length = r_len;
+ op->asym->dsa.s.data = (uint8_t *)(uintptr_t)s;
+ op->asym->dsa.s.length = s_len;
+ op->asym->dsa.y.data = group->y;
+ op->asym->dsa.y.length = group->y_len;
+ rte_crypto_op_attach_asym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+ *status = completed->status;
+ ret = 0;
+
+out:
+ rte_crypto_op_free(op);
+ if (session != NULL)
+ rte_cryptodev_asym_session_free(env.dev_id, session);
+ return ret;
+}
+
+static int
+process_dsa_p1363(json_t *root, const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *pub = json_object_get(group, "publicKey");
+ json_t *tests = json_object_get(group, "tests");
+ struct dsa_group dsa = { 0 };
+ const char *sha;
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_object(pub) || !json_is_array(tests))
+ return -EINVAL;
+ sha = json_string_value(json_object_get(group, "sha"));
+ if (sha == NULL || map_sha(sha, &dsa.hash, &dsa.digest_len) != 0) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (decode_hex(json_object_get(pub, "p"), &dsa.p, &dsa.p_len) != 0 ||
+ decode_hex(json_object_get(pub, "q"), &dsa.q, &dsa.q_len) != 0 ||
+ decode_hex(json_object_get(pub, "g"), &dsa.g, &dsa.g_len) != 0 ||
+ decode_hex(json_object_get(pub, "y"), &dsa.y, &dsa.y_len) != 0) {
+ rte_free(dsa.p);
+ rte_free(dsa.q);
+ rte_free(dsa.g);
+ rte_free(dsa.y);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *sig = NULL;
+ uint32_t sig_len = 0;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ json_t *tc_id;
+ int ret;
+
+ memset(&vector, 0, sizeof(vector));
+ tc_id = json_object_get(test, "tcId");
+ vector.result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector.result == NULL) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ vector.tc_id = json_integer_value(tc_id);
+ if (decode_hex(json_object_get(test, "msg"), &vector.msg,
+ &vector.msg_len) != 0 ||
+ decode_hex(json_object_get(test, "sig"),
+ &sig, &sig_len) != 0) {
+ rte_free(vector.msg);
+ rte_free(sig);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, &vector, "acceptable result policy");
+ rte_free(vector.msg);
+ rte_free(sig);
+ continue;
+ }
+ if ((sig_len & 1) != 0 || sig_len == 0) {
+ debug_vector("SKIP", name, &vector, "malformed P1363 signature");
+ stats->skipped_unsupported++;
+ rte_free(vector.msg);
+ rte_free(sig);
+ continue;
+ }
+ ret = run_dsa_verify(&dsa, vector.msg, vector.msg_len, sig, sig_len / 2,
+ sig + sig_len / 2, sig_len / 2, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ } else if (ret == -ETIMEDOUT) {
+ return -ETIMEDOUT;
+ } else if (ret == -EMSGSIZE) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "message length exceeds mbuf");
+ } else if ((strcmp(vector.result, "valid") == 0 &&
+ (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS)) ||
+ (strcmp(vector.result, "invalid") == 0 &&
+ (ret != 0 || status == RTE_CRYPTO_OP_STATUS_SUCCESS))) {
+ debug_vector("FAIL", name, &vector, "unexpected verification result");
+ stats->failed++;
+ } else if (strcmp(vector.result, "valid") == 0 ||
+ strcmp(vector.result, "invalid") == 0) {
+ stats->passed++;
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "unknown result");
+ }
+ rte_free(vector.msg);
+ rte_free(sig);
+ }
+ rte_free(dsa.p);
+ rte_free(dsa.q);
+ rte_free(dsa.g);
+ rte_free(dsa.y);
+ }
+ return 0;
+}
+
+static int
+map_curve(const char *name, enum rte_crypto_curve_id *curve, uint32_t *bytesize)
+{
+ if (strcmp(name, "secp224r1") == 0) {
+ *curve = RTE_CRYPTO_EC_GROUP_SECP224R1;
+ *bytesize = 28;
+ } else if (strcmp(name, "secp256r1") == 0) {
+ *curve = RTE_CRYPTO_EC_GROUP_SECP256R1;
+ *bytesize = 32;
+ } else if (strcmp(name, "secp384r1") == 0) {
+ *curve = RTE_CRYPTO_EC_GROUP_SECP384R1;
+ *bytesize = 48;
+ } else if (strcmp(name, "secp521r1") == 0) {
+ *curve = RTE_CRYPTO_EC_GROUP_SECP521R1;
+ *bytesize = 66;
+ } else {
+ return -ENOTSUP;
+ }
+ return 0;
+}
+
+/* Left-align an unsigned big-endian value into a fixed-width field. */
+static bool
+value_equals_padded(const uint8_t *a, uint32_t a_len, const uint8_t *b, uint32_t b_len)
+{
+ while (a_len > 0 && a[0] == 0) {
+ a++;
+ a_len--;
+ }
+ while (b_len > 0 && b[0] == 0) {
+ b++;
+ b_len--;
+ }
+ return a_len == b_len && memcmp(a, b, a_len) == 0;
+}
+
+/* Left-pad a big-endian value into a fixed-width field: strip leading zeros,
+ * reject values wider than the field, zero-pad the remaining prefix.
+ */
+static int
+normalize_operand(const uint8_t *value, uint32_t value_len, uint8_t *out, uint32_t bytesize)
+{
+ while (value_len > 0 && value[0] == 0) {
+ value++;
+ value_len--;
+ }
+ if (value_len > bytesize)
+ return -EINVAL;
+ memset(out, 0, bytesize);
+ memcpy(out + bytesize - value_len, value, value_len);
+ return 0;
+}
+
+static int
+run_ecdh_ecpoint(enum rte_crypto_curve_id curve, uint32_t bytesize,
+ const uint8_t *priv, uint32_t priv_len, const uint8_t *pub_x,
+ const uint8_t *pub_y, uint8_t **shared_x, uint32_t *shared_len,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_asym_xform xform = { 0 };
+ void *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_cryptodev_info dev_info;
+ struct rte_cryptodev_asym_capability_idx index = {
+ .type = RTE_CRYPTO_ASYM_XFORM_ECDH,
+ };
+ const struct rte_cryptodev_asymmetric_xform_capability *capability;
+ uint8_t *out_x = NULL;
+ uint8_t *out_y = NULL;
+ uint8_t priv_buf[MAX_EC_BYTESIZE];
+ int ret;
+
+ *shared_x = NULL;
+ if (env.asym_session_pool == NULL || env.asym_op_pool == NULL)
+ return -ENOTSUP;
+ rte_cryptodev_info_get(env.dev_id, &dev_info);
+ if ((dev_info.feature_flags & RTE_CRYPTODEV_FF_ASYMMETRIC_CRYPTO) == 0)
+ return -ENOTSUP;
+ capability = rte_cryptodev_asym_capability_get(env.dev_id, &index);
+ if (capability == NULL)
+ return -ENOTSUP;
+ if (rte_cryptodev_asym_xform_capability_check_optype(capability,
+ (enum rte_crypto_asym_op_type)
+ RTE_CRYPTO_ASYM_KE_SHARED_SECRET_COMPUTE) == 0)
+ return -ENOTSUP;
+
+ if (bytesize > sizeof(priv_buf))
+ return -ENOTSUP;
+ if (normalize_operand(priv, priv_len, priv_buf, bytesize) != 0)
+ return -EINVAL;
+
+ xform.xform_type = RTE_CRYPTO_ASYM_XFORM_ECDH;
+ xform.next = NULL;
+ xform.ec.curve_id = curve;
+ xform.ec.pkey.data = priv_buf;
+ xform.ec.pkey.length = bytesize;
+ xform.ec.q.x.data = (uint8_t *)(uintptr_t)pub_x;
+ xform.ec.q.x.length = bytesize;
+ xform.ec.q.y.data = (uint8_t *)(uintptr_t)pub_y;
+ xform.ec.q.y.length = bytesize;
+
+ ret = -ENOMEM;
+ out_x = rte_zmalloc(NULL, bytesize, 0);
+ out_y = rte_zmalloc(NULL, bytesize, 0);
+ if (out_x == NULL || out_y == NULL)
+ goto out;
+ /* Session-create failure after the capability check is a PMD failure. */
+ if (rte_cryptodev_asym_session_create(env.dev_id, &xform, env.asym_session_pool,
+ &session) < 0 || session == NULL) {
+ ret = -EIO;
+ goto out;
+ }
+ op = rte_crypto_op_alloc(env.asym_op_pool, RTE_CRYPTO_OP_TYPE_ASYMMETRIC);
+ if (op == NULL)
+ goto out;
+ op->asym->ecdh.ke_type = RTE_CRYPTO_ASYM_KE_SHARED_SECRET_COMPUTE;
+ op->asym->ecdh.priv_key.data = priv_buf;
+ op->asym->ecdh.priv_key.length = bytesize;
+ op->asym->ecdh.pub_key.x.data = (uint8_t *)(uintptr_t)pub_x;
+ op->asym->ecdh.pub_key.x.length = bytesize;
+ op->asym->ecdh.pub_key.y.data = (uint8_t *)(uintptr_t)pub_y;
+ op->asym->ecdh.pub_key.y.length = bytesize;
+ op->asym->ecdh.shared_secret.x.data = out_x;
+ op->asym->ecdh.shared_secret.x.length = bytesize;
+ op->asym->ecdh.shared_secret.y.data = out_y;
+ op->asym->ecdh.shared_secret.y.length = bytesize;
+ rte_crypto_op_attach_asym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+ *status = completed->status;
+ if (completed->status == RTE_CRYPTO_OP_STATUS_SUCCESS) {
+ *shared_x = out_x;
+ *shared_len = completed->asym->ecdh.shared_secret.x.length;
+ out_x = NULL;
+ }
+ ret = 0;
+
+out:
+ rte_free(out_x);
+ rte_free(out_y);
+ rte_crypto_op_free(op);
+ if (session != NULL)
+ rte_cryptodev_asym_session_free(env.dev_id, session);
+ return ret;
+}
+
+static int
+process_ecdh_ecpoint(json_t *root, const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *tests = json_object_get(group, "tests");
+ enum rte_crypto_curve_id curve;
+ uint32_t bytesize;
+ const char *curve_name;
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_array(tests))
+ return -EINVAL;
+ curve_name = json_string_value(json_object_get(group, "curve"));
+ if (curve_name == NULL || map_curve(curve_name, &curve, &bytesize) != 0) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *pub = NULL;
+ uint8_t *priv = NULL;
+ uint8_t *shared = NULL;
+ uint8_t *result_x = NULL;
+ uint32_t pub_len = 0, priv_len = 0, shared_len = 0, result_len = 0;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ bool matched;
+ json_t *tc_id;
+ int ret;
+
+ memset(&vector, 0, sizeof(vector));
+ tc_id = json_object_get(test, "tcId");
+ vector.result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector.result == NULL) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ vector.tc_id = json_integer_value(tc_id);
+ if (decode_hex(json_object_get(test, "public"), &pub, &pub_len) != 0 ||
+ decode_hex(json_object_get(test, "private"), &priv,
+ &priv_len) != 0 ||
+ decode_hex(json_object_get(test, "shared"), &shared,
+ &shared_len) != 0) {
+ rte_free(pub);
+ rte_free(priv);
+ rte_free(shared);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, &vector, "acceptable result policy");
+ goto next;
+ }
+ /* Only uncompressed points (0x04 || x || y) map to the DPDK EC point. */
+ if (pub_len != 1 + 2 * bytesize || pub[0] != 0x04) {
+ debug_vector("SKIP", name, &vector, "non-uncompressed public point");
+ stats->skipped_unsupported++;
+ goto next;
+ }
+ /* Invalid-curve rejection cases carry no expected shared secret;
+ * on-curve validation is a separate ECDH PUB_KEY_VERIFY op, not
+ * part of the raw shared-secret compute primitive.
+ */
+ if (shared_len == 0) {
+ debug_vector("SKIP", name, &vector,
+ "no expected shared secret (point validation out of scope)");
+ stats->skipped_unsupported++;
+ goto next;
+ }
+ ret = run_ecdh_ecpoint(curve, bytesize, priv, priv_len, pub + 1,
+ pub + 1 + bytesize, &result_x, &result_len, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ goto next;
+ }
+ if (ret == -ETIMEDOUT)
+ return -ETIMEDOUT;
+ if (ret == -EINVAL) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "operand wider than curve");
+ goto next;
+ }
+ matched = ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS &&
+ value_equals_padded(result_x, result_len, shared, shared_len);
+ if (strcmp(vector.result, "valid") == 0) {
+ if (ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS && matched) {
+ stats->passed++;
+ } else if (ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS) {
+ debug_vector("FAIL", name, &vector, "wrong shared secret");
+ stats->failed++;
+ } else {
+ /* PMD returned an error for a valid vector. */
+ debug_vector("FAIL", name, &vector, "PMD could not compute");
+ stats->failed++;
+ }
+ } else if (strcmp(vector.result, "invalid") == 0) {
+ if (matched) {
+ debug_vector("FAIL", name, &vector, "produced forbidden secret");
+ stats->failed++;
+ } else {
+ stats->passed++;
+ }
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "unknown result");
+ }
+next:
+ rte_free(result_x);
+ rte_free(pub);
+ rte_free(priv);
+ rte_free(shared);
+ }
+ }
+ return 0;
+}
+
+struct ecdsa_group {
+ uint8_t *wx;
+ uint8_t *wy;
+ uint32_t wx_len;
+ uint32_t wy_len;
+ enum rte_crypto_curve_id curve;
+ uint32_t bytesize;
+ enum rte_crypto_auth_algorithm hash;
+ uint32_t digest_len;
+};
+
+static int
+run_ecdsa_verify(const struct ecdsa_group *group, const uint8_t *msg, uint32_t msg_len,
+ const uint8_t *r, uint32_t r_len, const uint8_t *s, uint32_t s_len,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_asym_xform xform = { 0 };
+ void *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_cryptodev_info dev_info;
+ struct rte_cryptodev_asym_capability_idx index = {
+ .type = RTE_CRYPTO_ASYM_XFORM_ECDSA,
+ };
+ const struct rte_cryptodev_asymmetric_xform_capability *capability;
+ uint8_t digest[64];
+ uint8_t wx_buf[MAX_EC_BYTESIZE];
+ uint8_t wy_buf[MAX_EC_BYTESIZE];
+ uint32_t e_len;
+ int ret;
+
+ if (env.asym_session_pool == NULL || env.asym_op_pool == NULL)
+ return -ENOTSUP;
+ rte_cryptodev_info_get(env.dev_id, &dev_info);
+ if ((dev_info.feature_flags & RTE_CRYPTODEV_FF_ASYMMETRIC_CRYPTO) == 0)
+ return -ENOTSUP;
+ capability = rte_cryptodev_asym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_asym_xform_capability_check_optype(capability,
+ RTE_CRYPTO_ASYM_OP_VERIFY) == 0)
+ return -ENOTSUP;
+
+ ret = compute_hash(msg, msg_len, group->hash, digest, group->digest_len);
+ if (ret != 0)
+ return ret;
+ /* ECDSA uses e = leftmost Ln bits of the hash (byte-aligned truncation). */
+ e_len = group->digest_len > group->bytesize ? group->bytesize : group->digest_len;
+
+ if (group->bytesize > sizeof(wx_buf))
+ return -ENOTSUP;
+ if (normalize_operand(group->wx, group->wx_len, wx_buf, group->bytesize) != 0 ||
+ normalize_operand(group->wy, group->wy_len, wy_buf, group->bytesize) != 0)
+ return -EINVAL;
+
+ xform.xform_type = RTE_CRYPTO_ASYM_XFORM_ECDSA;
+ xform.next = NULL;
+ xform.ec.curve_id = group->curve;
+ xform.ec.q.x.data = wx_buf;
+ xform.ec.q.x.length = group->bytesize;
+ xform.ec.q.y.data = wy_buf;
+ xform.ec.q.y.length = group->bytesize;
+
+ ret = -ENOMEM;
+ /* Session-create failure after the capability check is a PMD failure. */
+ if (rte_cryptodev_asym_session_create(env.dev_id, &xform, env.asym_session_pool,
+ &session) < 0 || session == NULL) {
+ ret = -EIO;
+ goto out;
+ }
+ op = rte_crypto_op_alloc(env.asym_op_pool, RTE_CRYPTO_OP_TYPE_ASYMMETRIC);
+ if (op == NULL)
+ goto out;
+ op->asym->ecdsa.op_type = RTE_CRYPTO_ASYM_OP_VERIFY;
+ op->asym->ecdsa.message.data = digest;
+ op->asym->ecdsa.message.length = e_len;
+ op->asym->ecdsa.r.data = (uint8_t *)(uintptr_t)r;
+ op->asym->ecdsa.r.length = r_len;
+ op->asym->ecdsa.s.data = (uint8_t *)(uintptr_t)s;
+ op->asym->ecdsa.s.length = s_len;
+ rte_crypto_op_attach_asym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+ *status = completed->status;
+ ret = 0;
+
+out:
+ rte_crypto_op_free(op);
+ if (session != NULL)
+ rte_cryptodev_asym_session_free(env.dev_id, session);
+ return ret;
+}
+
+static int
+process_ecdsa_p1363(json_t *root, const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *pub = json_object_get(group, "publicKey");
+ json_t *tests = json_object_get(group, "tests");
+ struct ecdsa_group ec = { 0 };
+ const char *sha;
+ const char *curve_name;
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_object(pub) || !json_is_array(tests))
+ return -EINVAL;
+ sha = json_string_value(json_object_get(group, "sha"));
+ curve_name = json_string_value(json_object_get(pub, "curve"));
+ if (sha == NULL || curve_name == NULL ||
+ map_sha(sha, &ec.hash, &ec.digest_len) != 0 ||
+ map_curve(curve_name, &ec.curve, &ec.bytesize) != 0) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (decode_hex(json_object_get(pub, "wx"), &ec.wx, &ec.wx_len) != 0 ||
+ decode_hex(json_object_get(pub, "wy"), &ec.wy, &ec.wy_len) != 0) {
+ rte_free(ec.wx);
+ rte_free(ec.wy);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *sig = NULL;
+ uint32_t sig_len = 0;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ json_t *tc_id;
+ int ret;
+
+ memset(&vector, 0, sizeof(vector));
+ tc_id = json_object_get(test, "tcId");
+ vector.result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector.result == NULL) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ vector.tc_id = json_integer_value(tc_id);
+ if (decode_hex(json_object_get(test, "msg"), &vector.msg,
+ &vector.msg_len) != 0 ||
+ decode_hex(json_object_get(test, "sig"),
+ &sig, &sig_len) != 0) {
+ rte_free(vector.msg);
+ rte_free(sig);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, &vector, "acceptable result policy");
+ rte_free(vector.msg);
+ rte_free(sig);
+ continue;
+ }
+ if (sig_len != 2 * ec.bytesize) {
+ debug_vector("SKIP", name, &vector,
+ "non-canonical P1363 signature size");
+ stats->skipped_unsupported++;
+ rte_free(vector.msg);
+ rte_free(sig);
+ continue;
+ }
+ ret = run_ecdsa_verify(&ec, vector.msg, vector.msg_len, sig, sig_len / 2,
+ sig + sig_len / 2, sig_len / 2, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ } else if (ret == -ETIMEDOUT) {
+ return -ETIMEDOUT;
+ } else if (ret == -EMSGSIZE) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "message length exceeds mbuf");
+ } else if (ret == -EINVAL) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "operand wider than curve");
+ } else if (strcmp(vector.result, "valid") == 0) {
+ if (ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS)
+ stats->passed++;
+ else {
+ debug_vector("FAIL", name, &vector, "valid signature rejected");
+ stats->failed++;
+ }
+ } else if (strcmp(vector.result, "invalid") == 0) {
+ if (ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS) {
+ debug_vector("FAIL", name, &vector, "invalid signature accepted");
+ stats->failed++;
+ } else {
+ stats->passed++;
+ }
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "unknown result");
+ }
+ rte_free(vector.msg);
+ rte_free(sig);
+ }
+ rte_free(ec.wx);
+ rte_free(ec.wy);
+ }
+ return 0;
+}
+
+static int
+process_file(const char *path, struct app_stats *stats)
+{
+ json_error_t error;
+ json_t *root;
+ const char *algorithm;
+ const char *schema;
+ int ret;
+
+ root = json_load_file(path, 0, &error);
+ if (root == NULL) {
+ if (env.debug)
+ printf("SKIP %s: JSON error at line %d: %s\n", path, error.line,
+ error.text);
+ return 0;
+ }
+ algorithm = json_string_value(json_object_get(root, "algorithm"));
+ schema = json_string_value(json_object_get(root, "schema"));
+ if (algorithm == NULL || schema == NULL) {
+ debug_file("SKIP", path, "missing algorithm or schema");
+ stats->skipped_unsupported++;
+ json_decref(root);
+ return 0;
+ }
+ if (strcmp(algorithm, "AES-GCM") == 0 &&
+ strcmp(schema, "aead_test_schema_v1.json") == 0) {
+ printf("Processing AES-GCM vectors: %s\n", path);
+ ret = process_aead(root, RTE_CRYPTO_AEAD_AES_GCM, "AES-GCM", stats);
+ } else if (strcmp(algorithm, "AES-CCM") == 0 &&
+ strcmp(schema, "aead_test_schema_v1.json") == 0) {
+ printf("Processing AES-CCM vectors: %s\n", path);
+ ret = process_aead(root, RTE_CRYPTO_AEAD_AES_CCM, "AES-CCM", stats);
+ } else if (strcmp(algorithm, "CHACHA20-POLY1305") == 0 &&
+ strcmp(schema, "aead_test_schema_v1.json") == 0) {
+ printf("Processing ChaCha20-Poly1305 vectors: %s\n", path);
+ ret = process_aead(root, RTE_CRYPTO_AEAD_CHACHA20_POLY1305,
+ "ChaCha20-Poly1305", stats);
+ } else if (strcmp(algorithm, "SM4-GCM") == 0 &&
+ strcmp(schema, "aead_test_schema_v1.json") == 0) {
+ printf("Processing SM4-GCM vectors: %s\n", path);
+ ret = process_aead(root, RTE_CRYPTO_AEAD_SM4_GCM, "SM4-GCM", stats);
+ } else if (strcmp(algorithm, "HMACSHA256") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA256 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA256_HMAC, "HMAC-SHA256", stats);
+ } else if (strcmp(algorithm, "HMACSHA1") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA1 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA1_HMAC, "HMAC-SHA1", stats);
+ } else if (strcmp(algorithm, "HMACSHA224") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA224 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA224_HMAC, "HMAC-SHA224", stats);
+ } else if (strcmp(algorithm, "HMACSHA384") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA384 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA384_HMAC, "HMAC-SHA384", stats);
+ } else if (strcmp(algorithm, "HMACSHA512") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA512 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA512_HMAC, "HMAC-SHA512", stats);
+ } else if (strcmp(algorithm, "HMACSHA3-224") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA3-224 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA3_224_HMAC, "HMAC-SHA3-224", stats);
+ } else if (strcmp(algorithm, "HMACSHA3-256") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA3-256 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA3_256_HMAC, "HMAC-SHA3-256", stats);
+ } else if (strcmp(algorithm, "HMACSHA3-384") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA3-384 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA3_384_HMAC, "HMAC-SHA3-384", stats);
+ } else if (strcmp(algorithm, "HMACSHA3-512") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA3-512 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA3_512_HMAC, "HMAC-SHA3-512", stats);
+ } else if (strcmp(algorithm, "HMACSM3") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SM3 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SM3_HMAC, "HMAC-SM3", stats);
+ } else if (strcmp(algorithm, "AES-CMAC") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing AES-CMAC vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_AES_CMAC, "AES-CMAC", stats);
+ } else if (strcmp(algorithm, "AES-GMAC") == 0 &&
+ strcmp(schema, "mac_with_iv_test_schema_v1.json") == 0) {
+ printf("Processing AES-GMAC vectors: %s\n", path);
+ ret = process_gmac(root, stats);
+ } else if (strcmp(algorithm, "DSA") == 0 &&
+ strcmp(schema, "dsa_p1363_verify_schema_v1.json") == 0) {
+ printf("Processing DSA (P1363) vectors: %s\n", path);
+ ret = process_dsa_p1363(root, "DSA", stats);
+ } else if (strcmp(algorithm, "ECDH") == 0 &&
+ strcmp(schema, "ecdh_ecpoint_test_schema_v1.json") == 0) {
+ printf("Processing ECDH (ecpoint) vectors: %s\n", path);
+ ret = process_ecdh_ecpoint(root, "ECDH", stats);
+ } else if (strcmp(algorithm, "ECDSA") == 0 &&
+ strcmp(schema, "ecdsa_p1363_verify_schema_v1.json") == 0) {
+ printf("Processing ECDSA (P1363) vectors: %s\n", path);
+ ret = process_ecdsa_p1363(root, "ECDSA", stats);
+ } else if (strncmp(algorithm, "AES-", strlen("AES-")) == 0 ||
+ strncmp(algorithm, "SEED-", strlen("SEED-")) == 0 ||
+ strcmp(algorithm, "SM4-CCM") == 0 ||
+ strncmp(algorithm, "HMAC", strlen("HMAC")) == 0 ||
+ strncmp(algorithm, "RSA", strlen("RSA")) == 0 ||
+ strcmp(algorithm, "DSA") == 0 ||
+ strcmp(algorithm, "ECDH") == 0 ||
+ strcmp(algorithm, "ECDSA") == 0) {
+ debug_file("SKIP", path, algorithm);
+ stats->skipped_unsupported++;
+ ret = 0;
+ } else {
+ debug_file("SKIP", path, "unsupported vector file");
+ stats->skipped_unsupported++;
+ ret = 0;
+ }
+ json_decref(root);
+ return ret;
+}
+
+static int
+process_path(const char *path, struct app_stats *stats)
+{
+ struct stat path_stat;
+ DIR *directory;
+ struct dirent *entry;
+ int ret = 0;
+
+ if (stat(path, &path_stat) != 0)
+ return -errno;
+ if (!S_ISDIR(path_stat.st_mode))
+ return process_file(path, stats);
+
+ directory = opendir(path);
+ if (directory == NULL)
+ return -errno;
+ while ((entry = readdir(directory)) != NULL) {
+ char file_path[PATH_MAX];
+ size_t name_length = strlen(entry->d_name);
+
+ if (name_length < 6 || strcmp(entry->d_name + name_length - 5, ".json") != 0)
+ continue;
+ if (snprintf(file_path, sizeof(file_path), "%s/%s", path, entry->d_name) >=
+ (int)sizeof(file_path)) {
+ ret = -ENAMETOOLONG;
+ break;
+ }
+ ret = process_file(file_path, stats);
+ if (ret != 0)
+ break;
+ }
+ closedir(directory);
+ return ret;
+}
+
+int
+main(int argc, char **argv)
+{
+ struct app_stats stats = { 0 };
+ int eal_args;
+ int ret;
+
+ eal_args = rte_eal_init(argc, argv);
+ if (eal_args < 0)
+ rte_exit(EXIT_FAILURE, "Cannot initialize EAL\n");
+ argc -= eal_args;
+ argv += eal_args;
+
+ ret = parse_args(argc, argv);
+ if (ret != 0) {
+ usage(argv[0]);
+ ret = EXIT_FAILURE;
+ goto out_eal;
+ }
+ ret = app_init();
+ if (ret != 0) {
+ printf("Cannot initialize cryptodev %u: %s\n", env.dev_id, rte_strerror(-ret));
+ ret = EXIT_FAILURE;
+ goto out_eal;
+ }
+
+ ret = process_path(env.vectors_path, &stats);
+ if (ret != 0)
+ printf("Vector processing failed: %s\n", rte_strerror(-ret));
+ printf("Summary: passed=%" PRIu64 " failed=%" PRIu64
+ " skipped_capability=%" PRIu64 " skipped_acceptable=%" PRIu64
+ " skipped_unsupported=%" PRIu64 "\n", stats.passed, stats.failed,
+ stats.skipped_capability, stats.skipped_acceptable, stats.skipped_unsupported);
+ app_uninit();
+ ret = ret == 0 && stats.failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+
+out_eal:
+ rte_eal_cleanup();
+ return ret;
+}
diff --git a/examples/wycheproof_validation/meson.build b/examples/wycheproof_validation/meson.build
new file mode 100644
index 0000000000..7f18b4cef3
--- /dev/null
+++ b/examples/wycheproof_validation/meson.build
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026
+
+# meson file, for building this example as part of a main DPDK build.
+#
+# To build this example as a standalone application with an already-installed
+# DPDK instance, use 'make'
+
+deps += ['cryptodev']
+
+if not dpdk_conf.has('RTE_HAS_JANSSON')
+ build = false
+ message('Skipping wycheproof_validation: Jansson is required')
+else
+ ext_deps += jansson_dep
+ sources = files('main.c')
+endif
--
2.34.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v5] examples: add Wycheproof validation app
2026-09-22 14:47 ` [PATCH v4] " Kai Ji
@ 2026-09-22 16:05 ` Kai Ji
2026-09-22 16:26 ` Stephen Hemminger
2026-09-23 10:50 ` [PATCH v6] " Kai Ji
0 siblings, 2 replies; 17+ messages in thread
From: Kai Ji @ 2026-09-22 16:05 UTC (permalink / raw)
To: dev; +Cc: Kai Ji, Thomas Monjalon
Add a Wycheproof JSON vector validation example for cryptodev PMDs.
Support these algorithms when advertised by the selected PMD:
- AEAD: AES-GCM, AES-CCM, SM4-GCM, ChaCha20-Poly1305
- MAC: AES-CMAC, AES-GMAC, HMAC SHA-1/SHA-2/SHA-3/SM3
- Asymmetric: DSA (P1363 verify), ECDSA (P1363 verify),
ECDH (ecpoint shared-secret compute)
Validate valid vectors against generated ciphertexts, tags, plaintexts,
digests, shared secrets, or signature verification, and require the
expected rejection for invalid vectors. Digest inputs for DSA and ECDSA
use the symmetric auth path, selecting a separate symmetric-capable
device when the target device is asymmetric-only.
Skip parameter combinations outside PMD capability ranges and identify
recognized vector families without a compatible DPDK transform. A
--debug option lists every failed or skipped vector.
Add Meson and standalone build integration, with usage documentation.
Signed-off-by: Kai Ji <kai.ji@intel.com>
---
MAINTAINERS | 5 +
doc/guides/rel_notes/release_26_11.rst | 6 +
doc/guides/sample_app_ug/index.rst | 1 +
.../sample_app_ug/wycheproof_validation.rst | 45 +
examples/meson.build | 1 +
examples/wycheproof_validation/Makefile | 40 +
examples/wycheproof_validation/main.c | 2060 +++++++++++++++++
examples/wycheproof_validation/meson.build | 17 +
8 files changed, 2175 insertions(+)
create mode 100644 doc/guides/sample_app_ug/wycheproof_validation.rst
create mode 100644 examples/wycheproof_validation/Makefile
create mode 100644 examples/wycheproof_validation/main.c
create mode 100644 examples/wycheproof_validation/meson.build
diff --git a/MAINTAINERS b/MAINTAINERS
index 8c50c52933..d33923cbb2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2106,3 +2106,8 @@ F: examples/vmdq/
F: doc/guides/sample_app_ug/vmdq_forwarding.rst
F: examples/vmdq_dcb/
F: doc/guides/sample_app_ug/vmdq_dcb_forwarding.rst
+
+Wycheproof validation example
+M: Kai Ji <kai.ji@intel.com>
+F: examples/wycheproof_validation/
+F: doc/guides/sample_app_ug/wycheproof_validation.rst
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 4b3e5d995c..0126d25a58 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -55,6 +55,12 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **Added Wycheproof validation example application.**
+
+ Added a new example application that validates a DPDK cryptodev PMD against
+ the Google Wycheproof JSON test vectors, covering AEAD, MAC, DSA, ECDH and
+ ECDSA algorithm families.
+
Removed Items
-------------
diff --git a/doc/guides/sample_app_ug/index.rst b/doc/guides/sample_app_ug/index.rst
index f12623bb66..29313fe3de 100644
--- a/doc/guides/sample_app_ug/index.rst
+++ b/doc/guides/sample_app_ug/index.rst
@@ -53,6 +53,7 @@ Sample Applications User Guides
ptpclient
ptp_tap_relay_sw
fips_validation
+ wycheproof_validation
ipsec_secgw
bbdev_app
ntb
diff --git a/doc/guides/sample_app_ug/wycheproof_validation.rst b/doc/guides/sample_app_ug/wycheproof_validation.rst
new file mode 100644
index 0000000000..8d954625ac
--- /dev/null
+++ b/doc/guides/sample_app_ug/wycheproof_validation.rst
@@ -0,0 +1,45 @@
+.. SPDX-License-Identifier: BSD-3-Clause
+ Copyright(c) 2026 Intel Corporation.
+
+Wycheproof Validation Example
+=============================
+
+Overview
+--------
+
+This example validates a DPDK cryptodev implementation against the Google
+Wycheproof JSON test vectors.
+
+The application reads one JSON file or a directory of JSON files at runtime and
+checks the supported algorithm families against the selected PMD. It can be used
+with a PMD that advertises AEAD, MAC, DSA, ECDH, or ECDSA support.
+
+Build
+-----
+
+Build the example from the DPDK tree with Meson:
+
+.. code-block:: console
+
+ meson setup build -Dexamples=wycheproof_validation -Denable_drivers=crypto/openssl
+ meson compile -C build
+
+Standalone Makefile builds are also supported from the example directory.
+
+Run
+---
+
+Run the example with an OpenSSL-backed cryptodev and a vector file:
+
+.. code-block:: console
+
+ ./build/examples/dpdk-wycheproof_validation --vdev crypto_openssl -- \
+ --vectors ../wycheproof/testvectors_v1/aes_gcm_test.json \
+ --cryptodev crypto_openssl --debug
+
+The program accepts a single JSON file or a directory of JSON files. Use
+``--debug`` to print each failed or skipped vector with its reason.
+
+The exit code is nonzero when a parsed and supported vector fails validation;
+unsupported vectors, capability-skipped cases, and acceptable results are reported
+in the summary without forcing a failure.
diff --git a/examples/meson.build b/examples/meson.build
index 25d9c88457..000d05863a 100644
--- a/examples/meson.build
+++ b/examples/meson.build
@@ -59,6 +59,7 @@ all_examples = [
'vm_power_manager/guest_cli',
'vmdq',
'vmdq_dcb',
+ 'wycheproof_validation',
]
# on install, skip copying all meson.build files
diff --git a/examples/wycheproof_validation/Makefile b/examples/wycheproof_validation/Makefile
new file mode 100644
index 0000000000..8cc0293364
--- /dev/null
+++ b/examples/wycheproof_validation/Makefile
@@ -0,0 +1,40 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026
+
+APP = wycheproof_validation
+SRCS-y := main.c
+
+PKGCONF ?= pkg-config
+
+ifneq ($(shell $(PKGCONF) --exists libdpdk && echo 0),0)
+$(error "no installation of DPDK found")
+endif
+ifneq ($(shell $(PKGCONF) --exists jansson && echo 0),0)
+$(error "Jansson is required")
+endif
+
+all: shared
+.PHONY: shared static
+shared: build/$(APP)-shared
+ ln -sf $(APP)-shared build/$(APP)
+static: build/$(APP)-static
+ ln -sf $(APP)-static build/$(APP)
+
+PC_FILE := $(shell $(PKGCONF) --path libdpdk 2>/dev/null)
+CFLAGS += -O3 $(shell $(PKGCONF) --cflags libdpdk jansson)
+LDFLAGS_SHARED = $(shell $(PKGCONF) --libs libdpdk jansson)
+LDFLAGS_STATIC = $(shell $(PKGCONF) --static --libs libdpdk jansson)
+
+build/$(APP)-shared: $(SRCS-y) Makefile $(PC_FILE) | build
+ $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_SHARED)
+
+build/$(APP)-static: $(SRCS-y) Makefile $(PC_FILE) | build
+ $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_STATIC)
+
+build:
+ @mkdir -p $@
+
+.PHONY: clean
+clean:
+ rm -f build/$(APP) build/$(APP)-static build/$(APP)-shared
+ test -d build && rmdir -p build || true
diff --git a/examples/wycheproof_validation/main.c b/examples/wycheproof_validation/main.c
new file mode 100644
index 0000000000..3028112a44
--- /dev/null
+++ b/examples/wycheproof_validation/main.c
@@ -0,0 +1,2060 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026
+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <jansson.h>
+
+#include <rte_cryptodev.h>
+#include <rte_cycles.h>
+#include <rte_eal.h>
+#include <rte_errno.h>
+#include <rte_mbuf.h>
+#include <rte_mempool.h>
+#include <rte_malloc.h>
+
+#define DEFAULT_MBUF_DATA_ROOM 16384
+#define NUM_QP_DESCRIPTORS 128
+#define MAX_IV_LEN 512
+#define MAX_EC_BYTESIZE 66
+/* CCM AAD is prefixed with an 18-byte reserved area (B0 block + length). */
+#define AES_CCM_AAD_OFFSET 18
+#define IV_OFFSET (sizeof(struct rte_crypto_op) + sizeof(struct rte_crypto_sym_op))
+
+struct wycheproof_data {
+ uint8_t *key;
+ uint8_t *iv;
+ uint8_t *aad;
+ uint8_t *msg;
+ uint8_t *ct;
+ uint8_t *tag;
+ uint32_t key_len;
+ uint32_t iv_len;
+ uint32_t aad_len;
+ uint32_t msg_len;
+ uint32_t ct_len;
+ uint32_t tag_len;
+ json_int_t tc_id;
+ const char *result;
+};
+
+struct app_env {
+ uint8_t dev_id;
+ uint8_t hash_dev_id;
+ bool hash_dev_own;
+ uint32_t mbuf_data_room;
+ const char *vectors_path;
+ bool debug;
+ struct rte_mempool *mbuf_pool;
+ struct rte_mempool *session_pool;
+ struct rte_mempool *op_pool;
+ struct rte_mempool *asym_session_pool;
+ struct rte_mempool *asym_op_pool;
+};
+
+struct app_stats {
+ uint64_t passed;
+ uint64_t failed;
+ uint64_t skipped_capability;
+ uint64_t skipped_acceptable;
+ uint64_t skipped_unsupported;
+};
+
+static struct app_env env = {
+ .mbuf_data_room = DEFAULT_MBUF_DATA_ROOM,
+};
+
+static void
+usage(const char *program)
+{
+ printf("%s [EAL options] -- --vectors PATH [--cryptodev NAME | --cryptodev-id ID] [--debug]\n",
+ program);
+}
+
+static void
+debug_vector(const char *type, const char *algorithm,
+ const struct wycheproof_data *vector, const char *reason)
+{
+ if (env.debug)
+ printf("%s %s tcId=%" JSON_INTEGER_FORMAT " result=%s: %s\n", type,
+ algorithm, vector->tc_id, vector->result, reason);
+}
+
+static void
+debug_file(const char *type, const char *path, const char *reason)
+{
+ if (env.debug)
+ printf("%s %s: %s\n", type, path, reason);
+}
+
+static int
+parse_uint32(const char *text, uint32_t *value)
+{
+ char *end;
+ unsigned long parsed;
+
+ errno = 0;
+ parsed = strtoul(text, &end, 10);
+ if (errno != 0 || *text == '\0' || *end != '\0' || parsed > UINT32_MAX)
+ return -EINVAL;
+
+ *value = parsed;
+ return 0;
+}
+
+static int
+parse_args(int argc, char **argv)
+{
+ static const struct option options[] = {
+ { "vectors", required_argument, NULL, 'v' },
+ { "cryptodev", required_argument, NULL, 'c' },
+ { "cryptodev-id", required_argument, NULL, 'd' },
+ { "mbuf-dataroom", required_argument, NULL, 'm' },
+ { "debug", no_argument, NULL, 'D' },
+ { NULL, 0, NULL, 0 },
+ };
+ bool have_device = false;
+ int option;
+
+ if (rte_cryptodev_count() == 0)
+ return -ENODEV;
+ env.dev_id = 0;
+
+ while ((option = getopt_long(argc, argv, "v:c:d:m:D", options, NULL)) != -1) {
+ uint32_t value;
+ int dev_id;
+
+ switch (option) {
+ case 'v':
+ env.vectors_path = optarg;
+ break;
+ case 'c':
+ dev_id = rte_cryptodev_get_dev_id(optarg);
+ if (dev_id < 0)
+ return dev_id;
+ env.dev_id = dev_id;
+ have_device = true;
+ break;
+ case 'd':
+ if (parse_uint32(optarg, &value) != 0 || value > UINT8_MAX ||
+ !rte_cryptodev_is_valid_dev(value))
+ return -EINVAL;
+ env.dev_id = value;
+ have_device = true;
+ break;
+ case 'm':
+ if (parse_uint32(optarg, &env.mbuf_data_room) != 0 ||
+ env.mbuf_data_room < RTE_PKTMBUF_HEADROOM ||
+ env.mbuf_data_room > UINT16_MAX)
+ return -EINVAL;
+ break;
+ case 'D':
+ env.debug = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ if (env.vectors_path == NULL)
+ return -EINVAL;
+ if (!have_device)
+ printf("Using cryptodev %u (%s)\n", env.dev_id,
+ rte_cryptodev_name_get(env.dev_id));
+
+ return 0;
+}
+
+static int
+app_init(void)
+{
+ struct rte_cryptodev_config config = { rte_socket_id(), 1, 0 };
+ struct rte_cryptodev_qp_conf queue_pair = { NUM_QP_DESCRIPTORS, NULL };
+ struct rte_cryptodev_info dev_info;
+ uint32_t session_size;
+ int ret;
+
+ rte_cryptodev_info_get(env.dev_id, &dev_info);
+ if (dev_info.max_nb_queue_pairs < config.nb_queue_pairs)
+ return -ENOTSUP;
+
+ ret = rte_cryptodev_configure(env.dev_id, &config);
+ if (ret < 0)
+ return ret;
+
+ env.mbuf_pool = rte_pktmbuf_pool_create("WYCHEPROOF_MBUF_POOL", 64, 0, 0,
+ env.mbuf_data_room, rte_socket_id());
+ if (env.mbuf_pool == NULL) {
+ ret = -rte_errno;
+ goto error;
+ }
+
+ session_size = rte_cryptodev_sym_get_private_session_size(env.dev_id);
+ {
+ /* The hashing device may differ from the target device, so size the
+ * session pool for the largest sym session across all devices.
+ */
+ uint8_t d;
+
+ for (d = 0; d < rte_cryptodev_count(); d++) {
+ uint32_t s = rte_cryptodev_sym_get_private_session_size(d);
+
+ if (s > session_size)
+ session_size = s;
+ }
+ }
+ env.session_pool = rte_cryptodev_sym_session_pool_create("WYCHEPROOF_SESSION_POOL",
+ 64, session_size, 0, 0, rte_socket_id());
+ if (env.session_pool == NULL) {
+ ret = -rte_errno;
+ goto error;
+ }
+
+ /* priv_size is measured from the end of the sym op; the IV lives there. */
+ env.op_pool = rte_crypto_op_pool_create("WYCHEPROOF_OP_POOL",
+ RTE_CRYPTO_OP_TYPE_SYMMETRIC, 64, 0, MAX_IV_LEN, rte_socket_id());
+ if (env.op_pool == NULL) {
+ ret = -rte_errno;
+ goto error;
+ }
+
+ /* Asymmetric pools are best-effort; DSA/ECDSA paths gate on the
+ * device asymmetric feature flag at runtime.
+ */
+ env.asym_session_pool = rte_cryptodev_asym_session_pool_create(
+ "WYCHEPROOF_ASYM_SESS", 16, 0, 0, rte_socket_id());
+ env.asym_op_pool = rte_crypto_op_pool_create("WYCHEPROOF_ASYM_OP",
+ RTE_CRYPTO_OP_TYPE_ASYMMETRIC, 64, 0, 0, rte_socket_id());
+
+ queue_pair.mp_session = env.session_pool;
+ ret = rte_cryptodev_queue_pair_setup(env.dev_id, 0, &queue_pair,
+ rte_socket_id());
+ if (ret < 0)
+ goto error;
+
+ ret = rte_cryptodev_start(env.dev_id);
+ if (ret < 0)
+ goto error;
+
+ /* Digest computation for DSA/ECDSA needs a symmetric-auth device. The
+ * target device may be asym-only (for example QAT), so pick a separate
+ * sym-capable device for hashing when needed.
+ */
+ env.hash_dev_id = env.dev_id;
+ env.hash_dev_own = false;
+ {
+ struct rte_cryptodev_sym_capability_idx hash_idx = {
+ .type = RTE_CRYPTO_SYM_XFORM_AUTH,
+ .algo.auth = RTE_CRYPTO_AUTH_SHA256,
+ };
+ uint8_t count = rte_cryptodev_count();
+ uint8_t d;
+
+ if (rte_cryptodev_sym_capability_get(env.dev_id, &hash_idx) == NULL) {
+ for (d = 0; d < count; d++) {
+ struct rte_cryptodev_qp_conf hash_qp = { NUM_QP_DESCRIPTORS, NULL };
+
+ if (d == env.dev_id)
+ continue;
+ if (rte_cryptodev_sym_capability_get(d, &hash_idx) == NULL)
+ continue;
+ if (rte_cryptodev_configure(d, &config) < 0)
+ continue;
+ hash_qp.mp_session = env.session_pool;
+ if (rte_cryptodev_queue_pair_setup(d, 0, &hash_qp,
+ rte_socket_id()) < 0 ||
+ rte_cryptodev_start(d) < 0) {
+ rte_cryptodev_close(d);
+ continue;
+ }
+ env.hash_dev_id = d;
+ env.hash_dev_own = true;
+ break;
+ }
+ }
+ }
+
+ return 0;
+
+error:
+ rte_mempool_free(env.asym_op_pool);
+ rte_mempool_free(env.asym_session_pool);
+ rte_mempool_free(env.op_pool);
+ rte_mempool_free(env.session_pool);
+ rte_mempool_free(env.mbuf_pool);
+ env.asym_op_pool = NULL;
+ env.asym_session_pool = NULL;
+ env.op_pool = NULL;
+ env.session_pool = NULL;
+ env.mbuf_pool = NULL;
+ rte_cryptodev_close(env.dev_id);
+ return ret;
+}
+
+static void
+app_uninit(void)
+{
+ if (env.hash_dev_own) {
+ rte_cryptodev_stop(env.hash_dev_id);
+ rte_cryptodev_close(env.hash_dev_id);
+ }
+ rte_cryptodev_stop(env.dev_id);
+ rte_cryptodev_close(env.dev_id);
+ rte_mempool_free(env.asym_op_pool);
+ rte_mempool_free(env.asym_session_pool);
+ rte_mempool_free(env.op_pool);
+ rte_mempool_free(env.session_pool);
+ rte_mempool_free(env.mbuf_pool);
+}
+
+static void
+free_vector(struct wycheproof_data *vector)
+{
+ rte_free(vector->key);
+ rte_free(vector->iv);
+ rte_free(vector->aad);
+ rte_free(vector->msg);
+ rte_free(vector->ct);
+ rte_free(vector->tag);
+ memset(vector, 0, sizeof(*vector));
+}
+
+static int
+hex_value(char character)
+{
+ if (character >= '0' && character <= '9')
+ return character - '0';
+ if (character >= 'a' && character <= 'f')
+ return character - 'a' + 10;
+ if (character >= 'A' && character <= 'F')
+ return character - 'A' + 10;
+ return -1;
+}
+
+static int
+decode_hex(json_t *value, uint8_t **buffer, uint32_t *length)
+{
+ const char *hex;
+ size_t hex_length;
+ uint8_t *decoded;
+ size_t index;
+
+ if (!json_is_string(value))
+ return -EINVAL;
+
+ hex = json_string_value(value);
+ hex_length = strlen(hex);
+ if ((hex_length & 1) != 0 || hex_length / 2 > UINT32_MAX)
+ return -EINVAL;
+
+ *length = hex_length / 2;
+ if (*length == 0)
+ return 0;
+
+ decoded = rte_malloc(NULL, *length, 0);
+ if (decoded == NULL)
+ return -ENOMEM;
+
+ for (index = 0; index < *length; index++) {
+ int high = hex_value(hex[index * 2]);
+ int low = hex_value(hex[index * 2 + 1]);
+
+ if (high < 0 || low < 0) {
+ rte_free(decoded);
+ return -EINVAL;
+ }
+ decoded[index] = (high << 4) | low;
+ }
+
+ *buffer = decoded;
+ return 0;
+}
+
+static int
+parse_vector(json_t *test, struct wycheproof_data *vector)
+{
+ json_t *tc_id;
+ int ret;
+
+ memset(vector, 0, sizeof(*vector));
+ tc_id = json_object_get(test, "tcId");
+ vector->result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector->result == NULL)
+ return -EINVAL;
+ vector->tc_id = json_integer_value(tc_id);
+
+ ret = decode_hex(json_object_get(test, "key"), &vector->key, &vector->key_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "iv"), &vector->iv, &vector->iv_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "aad"), &vector->aad, &vector->aad_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "msg"), &vector->msg, &vector->msg_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "ct"), &vector->ct, &vector->ct_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "tag"), &vector->tag, &vector->tag_len);
+ if (ret != 0)
+ goto error;
+
+ return 0;
+
+error:
+ free_vector(vector);
+ return ret;
+}
+
+static int
+parse_mac_vector(json_t *test, struct wycheproof_data *vector)
+{
+ json_t *tc_id;
+ int ret;
+
+ memset(vector, 0, sizeof(*vector));
+ tc_id = json_object_get(test, "tcId");
+ vector->result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector->result == NULL)
+ return -EINVAL;
+ vector->tc_id = json_integer_value(tc_id);
+
+ ret = decode_hex(json_object_get(test, "key"), &vector->key, &vector->key_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "msg"), &vector->msg, &vector->msg_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "tag"), &vector->tag, &vector->tag_len);
+ if (ret != 0)
+ goto error;
+
+ return 0;
+
+error:
+ free_vector(vector);
+ return ret;
+}
+
+static int
+parse_mac_with_iv_vector(json_t *test, struct wycheproof_data *vector)
+{
+ int ret;
+
+ ret = parse_mac_vector(test, vector);
+ if (ret != 0)
+ return ret;
+ ret = decode_hex(json_object_get(test, "iv"), &vector->iv, &vector->iv_len);
+ if (ret != 0)
+ free_vector(vector);
+ return ret;
+}
+
+/* Bounded wait for one completed op; NULL on timeout so a stuck PMD fails the
+ * vector instead of hanging the tool.
+ */
+static struct rte_crypto_op *
+dequeue_one(uint8_t dev_id)
+{
+ struct rte_crypto_op *completed = NULL;
+ uint64_t deadline = rte_get_timer_cycles() + rte_get_timer_hz();
+
+ while (rte_cryptodev_dequeue_burst(dev_id, 0, &completed, 1) == 0) {
+ if (rte_get_timer_cycles() > deadline)
+ return NULL;
+ rte_pause();
+ }
+ return completed;
+}
+
+static int
+check_aead_capability(const struct wycheproof_data *vector,
+ enum rte_crypto_aead_algorithm algorithm)
+{
+ struct rte_cryptodev_sym_capability_idx index = {
+ .type = RTE_CRYPTO_SYM_XFORM_AEAD,
+ .algo.aead = algorithm,
+ };
+ const struct rte_cryptodev_symmetric_capability *capability;
+
+ capability = rte_cryptodev_sym_capability_get(env.dev_id, &index);
+ if (capability == NULL)
+ return -ENOTSUP;
+
+ return rte_cryptodev_sym_capability_check_aead(capability, vector->key_len,
+ vector->tag_len, vector->aad_len, vector->iv_len);
+}
+
+static int
+run_aead(const struct wycheproof_data *vector, enum rte_crypto_aead_algorithm algorithm,
+ enum rte_crypto_aead_operation operation, uint8_t **output, uint8_t **digest,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_sym_xform xform = { 0 };
+ struct rte_cryptodev_sym_session *session = NULL;
+ struct rte_crypto_op *operation_op = NULL;
+ struct rte_mbuf *mbuf = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_crypto_sym_op *sym_op;
+ uint8_t *aad = NULL;
+ uint8_t *input;
+ uint32_t input_len;
+ int ret = -ENOMEM;
+
+ *output = NULL;
+ *digest = NULL;
+ xform.type = RTE_CRYPTO_SYM_XFORM_AEAD;
+ xform.aead.algo = algorithm;
+ xform.aead.op = operation;
+ xform.aead.key.data = vector->key;
+ xform.aead.key.length = vector->key_len;
+ xform.aead.iv.offset = IV_OFFSET;
+ xform.aead.iv.length = vector->iv_len;
+ xform.aead.aad_length = vector->aad_len;
+ xform.aead.digest_length = vector->tag_len;
+
+ session = rte_cryptodev_sym_session_create(env.dev_id, &xform, env.session_pool);
+ if (session == NULL)
+ goto out;
+
+ operation_op = rte_crypto_op_alloc(env.op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC);
+ if (operation_op == NULL)
+ goto out;
+
+ mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
+ if (mbuf == NULL)
+ goto out;
+ input = operation == RTE_CRYPTO_AEAD_OP_ENCRYPT ? vector->msg : vector->ct;
+ input_len = operation == RTE_CRYPTO_AEAD_OP_ENCRYPT ? vector->msg_len : vector->ct_len;
+ if (input_len > rte_pktmbuf_tailroom(mbuf)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ if (input_len != 0)
+ memcpy(rte_pktmbuf_append(mbuf, input_len), input, input_len);
+
+ *digest = rte_malloc(NULL, vector->tag_len, RTE_CACHE_LINE_SIZE);
+ if (*digest == NULL && vector->tag_len != 0)
+ goto out;
+ if (operation == RTE_CRYPTO_AEAD_OP_DECRYPT && vector->tag_len != 0)
+ memcpy(*digest, vector->tag, vector->tag_len);
+
+ sym_op = operation_op->sym;
+ sym_op->m_src = mbuf;
+ sym_op->aead.data.offset = 0;
+ sym_op->aead.data.length = input_len;
+ if (algorithm == RTE_CRYPTO_AEAD_AES_CCM) {
+ aad = rte_zmalloc(NULL,
+ RTE_ALIGN_CEIL(vector->aad_len + AES_CCM_AAD_OFFSET, 16), 0);
+ if (aad == NULL)
+ goto out;
+ if (vector->aad_len != 0)
+ memcpy(aad + AES_CCM_AAD_OFFSET, vector->aad, vector->aad_len);
+ sym_op->aead.aad.data = aad;
+ sym_op->aead.aad.phys_addr = rte_malloc_virt2iova(aad);
+ memcpy(rte_crypto_op_ctod_offset(operation_op, uint8_t *, IV_OFFSET) + 1,
+ vector->iv, vector->iv_len);
+ } else {
+ sym_op->aead.aad.data = vector->aad;
+ sym_op->aead.aad.phys_addr = rte_malloc_virt2iova(vector->aad);
+ memcpy(rte_crypto_op_ctod_offset(operation_op, uint8_t *, IV_OFFSET), vector->iv,
+ vector->iv_len);
+ }
+ sym_op->aead.digest.data = *digest;
+ sym_op->aead.digest.phys_addr = rte_malloc_virt2iova(*digest);
+ rte_crypto_op_attach_sym_session(operation_op, session);
+
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &operation_op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+
+ *status = completed->status;
+ if (*status == RTE_CRYPTO_OP_STATUS_SUCCESS && input_len != 0) {
+ *output = rte_malloc(NULL, input_len, 0);
+ if (*output == NULL)
+ goto out;
+ memcpy(*output, rte_pktmbuf_mtod(mbuf, uint8_t *), input_len);
+ }
+ ret = 0;
+
+out:
+ rte_free(aad);
+ rte_pktmbuf_free(mbuf);
+ rte_crypto_op_free(operation_op);
+ rte_cryptodev_sym_session_free(env.dev_id, session);
+ if (ret != 0) {
+ rte_free(*output);
+ rte_free(*digest);
+ *output = NULL;
+ *digest = NULL;
+ }
+ return ret;
+}
+
+static int
+run_hmac(const struct wycheproof_data *vector, enum rte_crypto_auth_algorithm algorithm,
+ enum rte_crypto_auth_operation operation, uint8_t **digest,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_sym_xform xform = { 0 };
+ struct rte_cryptodev_sym_session *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_mbuf *mbuf = NULL;
+ const struct rte_cryptodev_symmetric_capability *capability;
+ struct rte_cryptodev_sym_capability_idx index = {
+ .type = RTE_CRYPTO_SYM_XFORM_AUTH,
+ .algo.auth = algorithm,
+ };
+ int ret = -ENOMEM;
+
+ *digest = NULL;
+ capability = rte_cryptodev_sym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_sym_capability_check_auth(capability,
+ vector->key_len, vector->tag_len, 0) != 0)
+ return -ENOTSUP;
+
+ xform.type = RTE_CRYPTO_SYM_XFORM_AUTH;
+ xform.auth.algo = algorithm;
+ xform.auth.op = operation;
+ xform.auth.key.data = vector->key;
+ xform.auth.key.length = vector->key_len;
+ xform.auth.digest_length = vector->tag_len;
+ session = rte_cryptodev_sym_session_create(env.dev_id, &xform, env.session_pool);
+ if (session == NULL)
+ goto out;
+
+ op = rte_crypto_op_alloc(env.op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC);
+ if (op == NULL)
+ goto out;
+ mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
+ if (mbuf == NULL)
+ goto out;
+ if (vector->msg_len > rte_pktmbuf_tailroom(mbuf)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ if (vector->msg_len != 0)
+ memcpy(rte_pktmbuf_append(mbuf, vector->msg_len), vector->msg, vector->msg_len);
+
+ *digest = rte_malloc(NULL, vector->tag_len, RTE_CACHE_LINE_SIZE);
+ if (*digest == NULL && vector->tag_len != 0)
+ goto out;
+ if (operation == RTE_CRYPTO_AUTH_OP_VERIFY && vector->tag_len != 0)
+ memcpy(*digest, vector->tag, vector->tag_len);
+
+ op->sym->m_src = mbuf;
+ op->sym->auth.data.offset = 0;
+ op->sym->auth.data.length = vector->msg_len;
+ op->sym->auth.digest.data = *digest;
+ op->sym->auth.digest.phys_addr = rte_malloc_virt2iova(*digest);
+ rte_crypto_op_attach_sym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+ *status = completed->status;
+ ret = 0;
+
+out:
+ rte_pktmbuf_free(mbuf);
+ rte_crypto_op_free(op);
+ rte_cryptodev_sym_session_free(env.dev_id, session);
+ if (ret != 0) {
+ rte_free(*digest);
+ *digest = NULL;
+ }
+ return ret;
+}
+
+static int
+run_gmac(const struct wycheproof_data *vector, enum rte_crypto_auth_operation operation,
+ uint8_t **digest, enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_sym_xform xform = { 0 };
+ struct rte_cryptodev_sym_session *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_mbuf *mbuf = NULL;
+ const struct rte_cryptodev_symmetric_capability *capability;
+ struct rte_cryptodev_sym_capability_idx index = {
+ .type = RTE_CRYPTO_SYM_XFORM_AUTH,
+ .algo.auth = RTE_CRYPTO_AUTH_AES_GMAC,
+ };
+ int ret = -ENOMEM;
+
+ *digest = NULL;
+ capability = rte_cryptodev_sym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_sym_capability_check_auth(capability,
+ vector->key_len, vector->tag_len, vector->iv_len) != 0)
+ return -ENOTSUP;
+
+ xform.type = RTE_CRYPTO_SYM_XFORM_AUTH;
+ xform.auth.algo = RTE_CRYPTO_AUTH_AES_GMAC;
+ xform.auth.op = operation;
+ xform.auth.key.data = vector->key;
+ xform.auth.key.length = vector->key_len;
+ xform.auth.iv.offset = IV_OFFSET;
+ xform.auth.iv.length = vector->iv_len;
+ xform.auth.digest_length = vector->tag_len;
+ session = rte_cryptodev_sym_session_create(env.dev_id, &xform, env.session_pool);
+ if (session == NULL)
+ goto out;
+
+ op = rte_crypto_op_alloc(env.op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC);
+ if (op == NULL)
+ goto out;
+ mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
+ if (mbuf == NULL)
+ goto out;
+ if (vector->msg_len > rte_pktmbuf_tailroom(mbuf)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ if (vector->msg_len != 0)
+ memcpy(rte_pktmbuf_append(mbuf, vector->msg_len), vector->msg, vector->msg_len);
+
+ *digest = rte_malloc(NULL, vector->tag_len, RTE_CACHE_LINE_SIZE);
+ if (*digest == NULL && vector->tag_len != 0)
+ goto out;
+ if (operation == RTE_CRYPTO_AUTH_OP_VERIFY && vector->tag_len != 0)
+ memcpy(*digest, vector->tag, vector->tag_len);
+
+ op->sym->m_src = mbuf;
+ op->sym->auth.data.offset = 0;
+ op->sym->auth.data.length = vector->msg_len;
+ op->sym->auth.digest.data = *digest;
+ op->sym->auth.digest.phys_addr = rte_malloc_virt2iova(*digest);
+ memcpy(rte_crypto_op_ctod_offset(op, uint8_t *, IV_OFFSET), vector->iv, vector->iv_len);
+ rte_crypto_op_attach_sym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+ *status = completed->status;
+ ret = 0;
+
+out:
+ rte_pktmbuf_free(mbuf);
+ rte_crypto_op_free(op);
+ rte_cryptodev_sym_session_free(env.dev_id, session);
+ if (ret != 0) {
+ rte_free(*digest);
+ *digest = NULL;
+ }
+ return ret;
+}
+
+static int
+validate_aead_vector(const struct wycheproof_data *vector,
+ enum rte_crypto_aead_algorithm algorithm, const char *name,
+ struct app_stats *stats)
+{
+ uint8_t *output = NULL;
+ uint8_t *digest = NULL;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ int ret;
+
+ if (strcmp(vector->result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, vector, "acceptable result policy");
+ return 0;
+ }
+ if (strcmp(vector->result, "valid") != 0 && strcmp(vector->result, "invalid") != 0) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, vector, "unknown result");
+ return 0;
+ }
+ /* CCM writes the IV one byte into the reserved area (after the flags byte). */
+ if (vector->iv_len + (algorithm == RTE_CRYPTO_AEAD_AES_CCM ? 1u : 0u) > MAX_IV_LEN ||
+ vector->msg_len != vector->ct_len ||
+ vector->msg_len > env.mbuf_data_room - RTE_PKTMBUF_HEADROOM) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, vector, "unsupported message length");
+ return 0;
+ }
+ if (check_aead_capability(vector, algorithm) != 0) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, vector, "PMD capability");
+ return 0;
+ }
+
+ if (strcmp(vector->result, "valid") == 0) {
+ ret = run_aead(vector, algorithm, RTE_CRYPTO_AEAD_OP_ENCRYPT, &output, &digest,
+ &status);
+ if (ret == -ETIMEDOUT)
+ return -ETIMEDOUT;
+ if (ret == -EMSGSIZE)
+ goto unsupported;
+ if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS ||
+ memcmp(output, vector->ct, vector->ct_len) != 0 ||
+ memcmp(digest, vector->tag, vector->tag_len) != 0)
+ goto failed;
+ rte_free(output);
+ rte_free(digest);
+ output = NULL;
+ digest = NULL;
+ ret = run_aead(vector, algorithm, RTE_CRYPTO_AEAD_OP_DECRYPT, &output, &digest,
+ &status);
+ if (ret == -ETIMEDOUT)
+ return -ETIMEDOUT;
+ if (ret == -EMSGSIZE)
+ goto unsupported;
+ if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS ||
+ memcmp(output, vector->msg, vector->msg_len) != 0)
+ goto failed;
+ } else {
+ ret = run_aead(vector, algorithm, RTE_CRYPTO_AEAD_OP_DECRYPT, &output, &digest,
+ &status);
+ if (ret == -ETIMEDOUT)
+ return -ETIMEDOUT;
+ if (ret == -EMSGSIZE)
+ goto unsupported;
+ if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_AUTH_FAILED)
+ goto failed;
+ }
+
+ stats->passed++;
+ rte_free(output);
+ rte_free(digest);
+ return 0;
+
+unsupported:
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, vector, "message length exceeds mbuf");
+ rte_free(output);
+ rte_free(digest);
+ return 0;
+
+failed:
+ if (env.debug)
+ printf("FAIL %s tcId=%" JSON_INTEGER_FORMAT " result=%s status=%d ret=%d\n",
+ name, vector->tc_id, vector->result, status, ret);
+ stats->failed++;
+ rte_free(output);
+ rte_free(digest);
+ return 0;
+}
+
+static int
+process_aead(json_t *root, enum rte_crypto_aead_algorithm algorithm,
+ const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+
+ json_array_foreach(groups, group_index, group) {
+ json_t *tests = json_object_get(group, "tests");
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_array(tests))
+ return -EINVAL;
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ int ret = parse_vector(test, &vector);
+
+ if (ret != 0) {
+ if (env.debug)
+ printf("SKIP %s group=%zu test=%zu: malformed vector\n",
+ name, group_index, test_index);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ ret = validate_aead_vector(&vector, algorithm, name, stats);
+ if (ret == -ETIMEDOUT)
+ return -ETIMEDOUT;
+ free_vector(&vector);
+ }
+ }
+
+ return 0;
+}
+
+static int
+process_hmac(json_t *root, enum rte_crypto_auth_algorithm algorithm,
+ const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *tests = json_object_get(group, "tests");
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_array(tests))
+ return -EINVAL;
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *digest = NULL;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ int ret;
+
+ ret = parse_mac_vector(test, &vector);
+ if (ret != 0) {
+ if (env.debug)
+ printf("SKIP %s group=%zu test=%zu: malformed vector\n",
+ name, group_index, test_index);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, &vector, "acceptable result policy");
+ } else if (strcmp(vector.result, "valid") == 0) {
+ ret = run_hmac(&vector, algorithm, RTE_CRYPTO_AUTH_OP_GENERATE,
+ &digest, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ } else if (ret == -ETIMEDOUT) {
+ return -ETIMEDOUT;
+ } else if (ret == -EMSGSIZE) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "message length exceeds mbuf");
+ } else if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS ||
+ memcmp(digest, vector.tag, vector.tag_len) != 0) {
+ debug_vector("FAIL", name, &vector, "generated tag mismatch");
+ stats->failed++;
+ } else
+ stats->passed++;
+ } else if (strcmp(vector.result, "invalid") == 0) {
+ ret = run_hmac(&vector, algorithm, RTE_CRYPTO_AUTH_OP_VERIFY,
+ &digest, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ } else if (ret == -ETIMEDOUT) {
+ return -ETIMEDOUT;
+ } else if (ret == -EMSGSIZE) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "message length exceeds mbuf");
+ } else if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_AUTH_FAILED) {
+ debug_vector("FAIL", name, &vector, "authentication unexpectedly succeeded");
+ stats->failed++;
+ } else
+ stats->passed++;
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "unknown result");
+ }
+ rte_free(digest);
+ free_vector(&vector);
+ }
+ }
+ return 0;
+}
+
+static int
+process_gmac(json_t *root, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *tests = json_object_get(group, "tests");
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_array(tests))
+ return -EINVAL;
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *digest = NULL;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ int ret;
+
+ ret = parse_mac_with_iv_vector(test, &vector);
+ if (ret != 0) {
+ if (env.debug)
+ printf("SKIP AES-GMAC group=%zu test=%zu: malformed vector\n",
+ group_index, test_index);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (vector.iv_len > MAX_IV_LEN) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", "AES-GMAC", &vector, "IV too large");
+ rte_free(digest);
+ free_vector(&vector);
+ continue;
+ }
+ if (strcmp(vector.result, "valid") == 0)
+ ret = run_gmac(&vector, RTE_CRYPTO_AUTH_OP_GENERATE,
+ &digest, &status);
+ else if (strcmp(vector.result, "invalid") == 0)
+ ret = run_gmac(&vector, RTE_CRYPTO_AUTH_OP_VERIFY,
+ &digest, &status);
+ else {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", "AES-GMAC", &vector, "acceptable result policy");
+ ret = 0;
+ }
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", "AES-GMAC", &vector, "PMD capability");
+ } else if (ret == -ETIMEDOUT) {
+ return -ETIMEDOUT;
+ } else if (ret == -EMSGSIZE) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", "AES-GMAC", &vector, "message length exceeds mbuf");
+ } else if ((strcmp(vector.result, "valid") == 0 &&
+ (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS ||
+ memcmp(digest, vector.tag, vector.tag_len) != 0)) ||
+ (strcmp(vector.result, "invalid") == 0 &&
+ (ret != 0 || status != RTE_CRYPTO_OP_STATUS_AUTH_FAILED))) {
+ debug_vector("FAIL", "AES-GMAC", &vector, "unexpected operation result");
+ stats->failed++;
+ } else if (strcmp(vector.result, "valid") == 0 ||
+ strcmp(vector.result, "invalid") == 0)
+ stats->passed++;
+ rte_free(digest);
+ free_vector(&vector);
+ }
+ }
+ return 0;
+}
+
+struct dsa_group {
+ uint8_t *p;
+ uint8_t *q;
+ uint8_t *g;
+ uint8_t *y;
+ uint32_t p_len;
+ uint32_t q_len;
+ uint32_t g_len;
+ uint32_t y_len;
+ enum rte_crypto_auth_algorithm hash;
+ uint32_t digest_len;
+};
+
+static int
+map_sha(const char *name, enum rte_crypto_auth_algorithm *algo, uint32_t *digest_len)
+{
+ if (strcmp(name, "SHA-1") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA1;
+ *digest_len = 20;
+ } else if (strcmp(name, "SHA-224") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA224;
+ *digest_len = 28;
+ } else if (strcmp(name, "SHA-256") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA256;
+ *digest_len = 32;
+ } else if (strcmp(name, "SHA-384") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA384;
+ *digest_len = 48;
+ } else if (strcmp(name, "SHA-512") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA512;
+ *digest_len = 64;
+ } else {
+ return -ENOTSUP;
+ }
+ return 0;
+}
+
+/* Compute a plain (keyless) message digest through the cryptodev auth path. */
+static int
+compute_hash(const uint8_t *msg, uint32_t msg_len, enum rte_crypto_auth_algorithm algo,
+ uint8_t *digest, uint32_t digest_len)
+{
+ struct rte_crypto_sym_xform xform = { 0 };
+ struct rte_cryptodev_sym_session *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_mbuf *mbuf = NULL;
+ const struct rte_cryptodev_symmetric_capability *capability;
+ struct rte_cryptodev_sym_capability_idx index = {
+ .type = RTE_CRYPTO_SYM_XFORM_AUTH,
+ .algo.auth = algo,
+ };
+ uint8_t *op_digest = NULL;
+ int ret = -ENOMEM;
+
+ capability = rte_cryptodev_sym_capability_get(env.hash_dev_id, &index);
+ if (capability == NULL || rte_cryptodev_sym_capability_check_auth(capability,
+ 0, digest_len, 0) != 0)
+ return -ENOTSUP;
+
+ xform.type = RTE_CRYPTO_SYM_XFORM_AUTH;
+ xform.auth.algo = algo;
+ xform.auth.op = RTE_CRYPTO_AUTH_OP_GENERATE;
+ xform.auth.digest_length = digest_len;
+ session = rte_cryptodev_sym_session_create(env.hash_dev_id, &xform, env.session_pool);
+ if (session == NULL)
+ goto out;
+
+ op = rte_crypto_op_alloc(env.op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC);
+ if (op == NULL)
+ goto out;
+ mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
+ if (mbuf == NULL)
+ goto out;
+ if (msg_len > rte_pktmbuf_tailroom(mbuf)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ if (msg_len != 0)
+ memcpy(rte_pktmbuf_append(mbuf, msg_len), msg, msg_len);
+
+ op_digest = rte_malloc(NULL, digest_len, RTE_CACHE_LINE_SIZE);
+ if (op_digest == NULL)
+ goto out;
+ op->sym->m_src = mbuf;
+ op->sym->auth.data.offset = 0;
+ op->sym->auth.data.length = msg_len;
+ op->sym->auth.digest.data = op_digest;
+ op->sym->auth.digest.phys_addr = rte_malloc_virt2iova(op_digest);
+ rte_crypto_op_attach_sym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.hash_dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.hash_dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+ if (completed->status != RTE_CRYPTO_OP_STATUS_SUCCESS) {
+ ret = -EIO;
+ goto out;
+ }
+ memcpy(digest, op_digest, digest_len);
+ ret = 0;
+
+out:
+ rte_free(op_digest);
+ rte_pktmbuf_free(mbuf);
+ rte_crypto_op_free(op);
+ rte_cryptodev_sym_session_free(env.hash_dev_id, session);
+ return ret;
+}
+
+static int
+run_dsa_verify(const struct dsa_group *group, const uint8_t *msg, uint32_t msg_len,
+ const uint8_t *r, uint32_t r_len, const uint8_t *s, uint32_t s_len,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_asym_xform xform = { 0 };
+ void *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_cryptodev_info dev_info;
+ struct rte_cryptodev_asym_capability_idx index = {
+ .type = RTE_CRYPTO_ASYM_XFORM_DSA,
+ };
+ const struct rte_cryptodev_asymmetric_xform_capability *capability;
+ uint8_t digest[64];
+ int ret;
+
+ if (env.asym_session_pool == NULL || env.asym_op_pool == NULL)
+ return -ENOTSUP;
+ rte_cryptodev_info_get(env.dev_id, &dev_info);
+ if ((dev_info.feature_flags & RTE_CRYPTODEV_FF_ASYMMETRIC_CRYPTO) == 0)
+ return -ENOTSUP;
+ capability = rte_cryptodev_asym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_asym_xform_capability_check_optype(capability,
+ RTE_CRYPTO_ASYM_OP_VERIFY) == 0)
+ return -ENOTSUP;
+
+ ret = compute_hash(msg, msg_len, group->hash, digest, group->digest_len);
+ if (ret != 0)
+ return ret;
+
+ xform.xform_type = RTE_CRYPTO_ASYM_XFORM_DSA;
+ xform.next = NULL;
+ xform.dsa.p.data = group->p;
+ xform.dsa.p.length = group->p_len;
+ xform.dsa.q.data = group->q;
+ xform.dsa.q.length = group->q_len;
+ xform.dsa.g.data = group->g;
+ xform.dsa.g.length = group->g_len;
+
+ /* Coarse capability check cannot confirm every parameter; treat a
+ * session-create failure as unsupported rather than a hard failure.
+ */
+ if (rte_cryptodev_asym_session_create(env.dev_id, &xform, env.asym_session_pool,
+ &session) < 0 || session == NULL) {
+ ret = -ENOTSUP;
+ goto out;
+ }
+ ret = -ENOMEM;
+ op = rte_crypto_op_alloc(env.asym_op_pool, RTE_CRYPTO_OP_TYPE_ASYMMETRIC);
+ if (op == NULL)
+ goto out;
+ op->asym->dsa.op_type = RTE_CRYPTO_ASYM_OP_VERIFY;
+ /* DSA verify operates on the message digest. */
+ op->asym->dsa.message.data = digest;
+ op->asym->dsa.message.length = group->digest_len;
+ op->asym->dsa.r.data = (uint8_t *)(uintptr_t)r;
+ op->asym->dsa.r.length = r_len;
+ op->asym->dsa.s.data = (uint8_t *)(uintptr_t)s;
+ op->asym->dsa.s.length = s_len;
+ op->asym->dsa.y.data = group->y;
+ op->asym->dsa.y.length = group->y_len;
+ rte_crypto_op_attach_asym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+ *status = completed->status;
+ ret = 0;
+
+out:
+ rte_crypto_op_free(op);
+ if (session != NULL)
+ rte_cryptodev_asym_session_free(env.dev_id, session);
+ return ret;
+}
+
+static int
+process_dsa_p1363(json_t *root, const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *pub = json_object_get(group, "publicKey");
+ json_t *tests = json_object_get(group, "tests");
+ struct dsa_group dsa = { 0 };
+ const char *sha;
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_object(pub) || !json_is_array(tests))
+ return -EINVAL;
+ sha = json_string_value(json_object_get(group, "sha"));
+ if (sha == NULL || map_sha(sha, &dsa.hash, &dsa.digest_len) != 0) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (decode_hex(json_object_get(pub, "p"), &dsa.p, &dsa.p_len) != 0 ||
+ decode_hex(json_object_get(pub, "q"), &dsa.q, &dsa.q_len) != 0 ||
+ decode_hex(json_object_get(pub, "g"), &dsa.g, &dsa.g_len) != 0 ||
+ decode_hex(json_object_get(pub, "y"), &dsa.y, &dsa.y_len) != 0) {
+ rte_free(dsa.p);
+ rte_free(dsa.q);
+ rte_free(dsa.g);
+ rte_free(dsa.y);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *sig = NULL;
+ uint32_t sig_len = 0;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ json_t *tc_id;
+ int ret;
+
+ memset(&vector, 0, sizeof(vector));
+ tc_id = json_object_get(test, "tcId");
+ vector.result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector.result == NULL) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ vector.tc_id = json_integer_value(tc_id);
+ if (decode_hex(json_object_get(test, "msg"), &vector.msg,
+ &vector.msg_len) != 0 ||
+ decode_hex(json_object_get(test, "sig"),
+ &sig, &sig_len) != 0) {
+ rte_free(vector.msg);
+ rte_free(sig);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, &vector, "acceptable result policy");
+ rte_free(vector.msg);
+ rte_free(sig);
+ continue;
+ }
+ if ((sig_len & 1) != 0 || sig_len == 0) {
+ debug_vector("SKIP", name, &vector, "malformed P1363 signature");
+ stats->skipped_unsupported++;
+ rte_free(vector.msg);
+ rte_free(sig);
+ continue;
+ }
+ ret = run_dsa_verify(&dsa, vector.msg, vector.msg_len, sig, sig_len / 2,
+ sig + sig_len / 2, sig_len / 2, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ } else if (ret == -ETIMEDOUT) {
+ return -ETIMEDOUT;
+ } else if (ret == -EMSGSIZE) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "message length exceeds mbuf");
+ } else if ((strcmp(vector.result, "valid") == 0 &&
+ (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS)) ||
+ (strcmp(vector.result, "invalid") == 0 &&
+ (ret != 0 || status == RTE_CRYPTO_OP_STATUS_SUCCESS))) {
+ debug_vector("FAIL", name, &vector, "unexpected verification result");
+ stats->failed++;
+ } else if (strcmp(vector.result, "valid") == 0 ||
+ strcmp(vector.result, "invalid") == 0) {
+ stats->passed++;
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "unknown result");
+ }
+ rte_free(vector.msg);
+ rte_free(sig);
+ }
+ rte_free(dsa.p);
+ rte_free(dsa.q);
+ rte_free(dsa.g);
+ rte_free(dsa.y);
+ }
+ return 0;
+}
+
+static int
+map_curve(const char *name, enum rte_crypto_curve_id *curve, uint32_t *bytesize)
+{
+ if (strcmp(name, "secp224r1") == 0) {
+ *curve = RTE_CRYPTO_EC_GROUP_SECP224R1;
+ *bytesize = 28;
+ } else if (strcmp(name, "secp256r1") == 0) {
+ *curve = RTE_CRYPTO_EC_GROUP_SECP256R1;
+ *bytesize = 32;
+ } else if (strcmp(name, "secp384r1") == 0) {
+ *curve = RTE_CRYPTO_EC_GROUP_SECP384R1;
+ *bytesize = 48;
+ } else if (strcmp(name, "secp521r1") == 0) {
+ *curve = RTE_CRYPTO_EC_GROUP_SECP521R1;
+ *bytesize = 66;
+ } else {
+ return -ENOTSUP;
+ }
+ return 0;
+}
+
+/* Left-align an unsigned big-endian value into a fixed-width field. */
+static bool
+value_equals_padded(const uint8_t *a, uint32_t a_len, const uint8_t *b, uint32_t b_len)
+{
+ while (a_len > 0 && a[0] == 0) {
+ a++;
+ a_len--;
+ }
+ while (b_len > 0 && b[0] == 0) {
+ b++;
+ b_len--;
+ }
+ return a_len == b_len && memcmp(a, b, a_len) == 0;
+}
+
+/* Left-pad a big-endian value into a fixed-width field: strip leading zeros,
+ * reject values wider than the field, zero-pad the remaining prefix.
+ */
+static int
+normalize_operand(const uint8_t *value, uint32_t value_len, uint8_t *out, uint32_t bytesize)
+{
+ while (value_len > 0 && value[0] == 0) {
+ value++;
+ value_len--;
+ }
+ if (value_len > bytesize)
+ return -EINVAL;
+ memset(out, 0, bytesize);
+ memcpy(out + bytesize - value_len, value, value_len);
+ return 0;
+}
+
+static int
+run_ecdh_ecpoint(enum rte_crypto_curve_id curve, uint32_t bytesize,
+ const uint8_t *priv, uint32_t priv_len, const uint8_t *pub_x,
+ const uint8_t *pub_y, uint8_t **shared_x, uint32_t *shared_len,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_asym_xform xform = { 0 };
+ void *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_cryptodev_info dev_info;
+ struct rte_cryptodev_asym_capability_idx index = {
+ .type = RTE_CRYPTO_ASYM_XFORM_ECDH,
+ };
+ const struct rte_cryptodev_asymmetric_xform_capability *capability;
+ uint8_t *out_x = NULL;
+ uint8_t *out_y = NULL;
+ uint8_t priv_buf[MAX_EC_BYTESIZE];
+ int ret;
+
+ *shared_x = NULL;
+ if (env.asym_session_pool == NULL || env.asym_op_pool == NULL)
+ return -ENOTSUP;
+ rte_cryptodev_info_get(env.dev_id, &dev_info);
+ if ((dev_info.feature_flags & RTE_CRYPTODEV_FF_ASYMMETRIC_CRYPTO) == 0)
+ return -ENOTSUP;
+ capability = rte_cryptodev_asym_capability_get(env.dev_id, &index);
+ if (capability == NULL)
+ return -ENOTSUP;
+ if (rte_cryptodev_asym_xform_capability_check_optype(capability,
+ (enum rte_crypto_asym_op_type)
+ RTE_CRYPTO_ASYM_KE_SHARED_SECRET_COMPUTE) == 0)
+ return -ENOTSUP;
+
+ if (bytesize > sizeof(priv_buf))
+ return -ENOTSUP;
+ if (normalize_operand(priv, priv_len, priv_buf, bytesize) != 0)
+ return -EINVAL;
+
+ xform.xform_type = RTE_CRYPTO_ASYM_XFORM_ECDH;
+ xform.next = NULL;
+ xform.ec.curve_id = curve;
+ xform.ec.pkey.data = priv_buf;
+ xform.ec.pkey.length = bytesize;
+ xform.ec.q.x.data = (uint8_t *)(uintptr_t)pub_x;
+ xform.ec.q.x.length = bytesize;
+ xform.ec.q.y.data = (uint8_t *)(uintptr_t)pub_y;
+ xform.ec.q.y.length = bytesize;
+
+ ret = -ENOMEM;
+ out_x = rte_zmalloc(NULL, bytesize, 0);
+ out_y = rte_zmalloc(NULL, bytesize, 0);
+ if (out_x == NULL || out_y == NULL)
+ goto out;
+ /* Coarse capability check cannot confirm curve support; treat a
+ * session-create failure as unsupported rather than a hard failure.
+ */
+ if (rte_cryptodev_asym_session_create(env.dev_id, &xform, env.asym_session_pool,
+ &session) < 0 || session == NULL) {
+ ret = -ENOTSUP;
+ goto out;
+ }
+ op = rte_crypto_op_alloc(env.asym_op_pool, RTE_CRYPTO_OP_TYPE_ASYMMETRIC);
+ if (op == NULL)
+ goto out;
+ op->asym->ecdh.ke_type = RTE_CRYPTO_ASYM_KE_SHARED_SECRET_COMPUTE;
+ op->asym->ecdh.priv_key.data = priv_buf;
+ op->asym->ecdh.priv_key.length = bytesize;
+ op->asym->ecdh.pub_key.x.data = (uint8_t *)(uintptr_t)pub_x;
+ op->asym->ecdh.pub_key.x.length = bytesize;
+ op->asym->ecdh.pub_key.y.data = (uint8_t *)(uintptr_t)pub_y;
+ op->asym->ecdh.pub_key.y.length = bytesize;
+ op->asym->ecdh.shared_secret.x.data = out_x;
+ op->asym->ecdh.shared_secret.x.length = bytesize;
+ op->asym->ecdh.shared_secret.y.data = out_y;
+ op->asym->ecdh.shared_secret.y.length = bytesize;
+ rte_crypto_op_attach_asym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+ *status = completed->status;
+ if (completed->status == RTE_CRYPTO_OP_STATUS_SUCCESS) {
+ *shared_x = out_x;
+ *shared_len = completed->asym->ecdh.shared_secret.x.length;
+ out_x = NULL;
+ }
+ ret = 0;
+
+out:
+ rte_free(out_x);
+ rte_free(out_y);
+ rte_crypto_op_free(op);
+ if (session != NULL)
+ rte_cryptodev_asym_session_free(env.dev_id, session);
+ return ret;
+}
+
+static int
+process_ecdh_ecpoint(json_t *root, const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *tests = json_object_get(group, "tests");
+ enum rte_crypto_curve_id curve;
+ uint32_t bytesize;
+ const char *curve_name;
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_array(tests))
+ return -EINVAL;
+ curve_name = json_string_value(json_object_get(group, "curve"));
+ if (curve_name == NULL || map_curve(curve_name, &curve, &bytesize) != 0) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *pub = NULL;
+ uint8_t *priv = NULL;
+ uint8_t *shared = NULL;
+ uint8_t *result_x = NULL;
+ uint32_t pub_len = 0, priv_len = 0, shared_len = 0, result_len = 0;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ bool matched;
+ json_t *tc_id;
+ int ret;
+
+ memset(&vector, 0, sizeof(vector));
+ tc_id = json_object_get(test, "tcId");
+ vector.result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector.result == NULL) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ vector.tc_id = json_integer_value(tc_id);
+ if (decode_hex(json_object_get(test, "public"), &pub, &pub_len) != 0 ||
+ decode_hex(json_object_get(test, "private"), &priv,
+ &priv_len) != 0 ||
+ decode_hex(json_object_get(test, "shared"), &shared,
+ &shared_len) != 0) {
+ rte_free(pub);
+ rte_free(priv);
+ rte_free(shared);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, &vector, "acceptable result policy");
+ goto next;
+ }
+ /* Only uncompressed points (0x04 || x || y) map to the DPDK EC point. */
+ if (pub_len != 1 + 2 * bytesize || pub[0] != 0x04) {
+ debug_vector("SKIP", name, &vector, "non-uncompressed public point");
+ stats->skipped_unsupported++;
+ goto next;
+ }
+ /* Invalid-curve rejection cases carry no expected shared secret;
+ * on-curve validation is a separate ECDH PUB_KEY_VERIFY op, not
+ * part of the raw shared-secret compute primitive.
+ */
+ if (shared_len == 0) {
+ debug_vector("SKIP", name, &vector,
+ "no expected shared secret (point validation out of scope)");
+ stats->skipped_unsupported++;
+ goto next;
+ }
+ ret = run_ecdh_ecpoint(curve, bytesize, priv, priv_len, pub + 1,
+ pub + 1 + bytesize, &result_x, &result_len, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ goto next;
+ }
+ if (ret == -ETIMEDOUT)
+ return -ETIMEDOUT;
+ if (ret == -EINVAL) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "operand wider than curve");
+ goto next;
+ }
+ matched = ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS &&
+ value_equals_padded(result_x, result_len, shared, shared_len);
+ if (strcmp(vector.result, "valid") == 0) {
+ if (ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS && matched) {
+ stats->passed++;
+ } else if (ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS) {
+ debug_vector("FAIL", name, &vector, "wrong shared secret");
+ stats->failed++;
+ } else {
+ /* PMD returned an error for a valid vector. */
+ debug_vector("FAIL", name, &vector, "PMD could not compute");
+ stats->failed++;
+ }
+ } else if (strcmp(vector.result, "invalid") == 0) {
+ if (matched) {
+ debug_vector("FAIL", name, &vector, "produced forbidden secret");
+ stats->failed++;
+ } else {
+ stats->passed++;
+ }
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "unknown result");
+ }
+next:
+ rte_free(result_x);
+ rte_free(pub);
+ rte_free(priv);
+ rte_free(shared);
+ }
+ }
+ return 0;
+}
+
+struct ecdsa_group {
+ uint8_t *wx;
+ uint8_t *wy;
+ uint32_t wx_len;
+ uint32_t wy_len;
+ enum rte_crypto_curve_id curve;
+ uint32_t bytesize;
+ enum rte_crypto_auth_algorithm hash;
+ uint32_t digest_len;
+};
+
+static int
+run_ecdsa_verify(const struct ecdsa_group *group, const uint8_t *msg, uint32_t msg_len,
+ const uint8_t *r, uint32_t r_len, const uint8_t *s, uint32_t s_len,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_asym_xform xform = { 0 };
+ void *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_cryptodev_info dev_info;
+ struct rte_cryptodev_asym_capability_idx index = {
+ .type = RTE_CRYPTO_ASYM_XFORM_ECDSA,
+ };
+ const struct rte_cryptodev_asymmetric_xform_capability *capability;
+ uint8_t digest[64];
+ uint8_t wx_buf[MAX_EC_BYTESIZE];
+ uint8_t wy_buf[MAX_EC_BYTESIZE];
+ uint32_t e_len;
+ int ret;
+
+ if (env.asym_session_pool == NULL || env.asym_op_pool == NULL)
+ return -ENOTSUP;
+ rte_cryptodev_info_get(env.dev_id, &dev_info);
+ if ((dev_info.feature_flags & RTE_CRYPTODEV_FF_ASYMMETRIC_CRYPTO) == 0)
+ return -ENOTSUP;
+ capability = rte_cryptodev_asym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_asym_xform_capability_check_optype(capability,
+ RTE_CRYPTO_ASYM_OP_VERIFY) == 0)
+ return -ENOTSUP;
+
+ ret = compute_hash(msg, msg_len, group->hash, digest, group->digest_len);
+ if (ret != 0)
+ return ret;
+ /* ECDSA uses e = leftmost Ln bits of the hash (byte-aligned truncation). */
+ e_len = group->digest_len > group->bytesize ? group->bytesize : group->digest_len;
+
+ if (group->bytesize > sizeof(wx_buf))
+ return -ENOTSUP;
+ if (normalize_operand(group->wx, group->wx_len, wx_buf, group->bytesize) != 0 ||
+ normalize_operand(group->wy, group->wy_len, wy_buf, group->bytesize) != 0)
+ return -EINVAL;
+
+ xform.xform_type = RTE_CRYPTO_ASYM_XFORM_ECDSA;
+ xform.next = NULL;
+ xform.ec.curve_id = group->curve;
+ xform.ec.q.x.data = wx_buf;
+ xform.ec.q.x.length = group->bytesize;
+ xform.ec.q.y.data = wy_buf;
+ xform.ec.q.y.length = group->bytesize;
+
+ /* Coarse capability check cannot confirm curve support; treat a
+ * session-create failure as unsupported rather than a hard failure.
+ */
+ if (rte_cryptodev_asym_session_create(env.dev_id, &xform, env.asym_session_pool,
+ &session) < 0 || session == NULL) {
+ ret = -ENOTSUP;
+ goto out;
+ }
+ ret = -ENOMEM;
+ op = rte_crypto_op_alloc(env.asym_op_pool, RTE_CRYPTO_OP_TYPE_ASYMMETRIC);
+ if (op == NULL)
+ goto out;
+ op->asym->ecdsa.op_type = RTE_CRYPTO_ASYM_OP_VERIFY;
+ op->asym->ecdsa.message.data = digest;
+ op->asym->ecdsa.message.length = e_len;
+ op->asym->ecdsa.r.data = (uint8_t *)(uintptr_t)r;
+ op->asym->ecdsa.r.length = r_len;
+ op->asym->ecdsa.s.data = (uint8_t *)(uintptr_t)s;
+ op->asym->ecdsa.s.length = s_len;
+ rte_crypto_op_attach_asym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+ *status = completed->status;
+ ret = 0;
+
+out:
+ rte_crypto_op_free(op);
+ if (session != NULL)
+ rte_cryptodev_asym_session_free(env.dev_id, session);
+ return ret;
+}
+
+static int
+process_ecdsa_p1363(json_t *root, const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *pub = json_object_get(group, "publicKey");
+ json_t *tests = json_object_get(group, "tests");
+ struct ecdsa_group ec = { 0 };
+ const char *sha;
+ const char *curve_name;
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_object(pub) || !json_is_array(tests))
+ return -EINVAL;
+ sha = json_string_value(json_object_get(group, "sha"));
+ curve_name = json_string_value(json_object_get(pub, "curve"));
+ if (sha == NULL || curve_name == NULL ||
+ map_sha(sha, &ec.hash, &ec.digest_len) != 0 ||
+ map_curve(curve_name, &ec.curve, &ec.bytesize) != 0) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (decode_hex(json_object_get(pub, "wx"), &ec.wx, &ec.wx_len) != 0 ||
+ decode_hex(json_object_get(pub, "wy"), &ec.wy, &ec.wy_len) != 0) {
+ rte_free(ec.wx);
+ rte_free(ec.wy);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *sig = NULL;
+ uint32_t sig_len = 0;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ json_t *tc_id;
+ int ret;
+
+ memset(&vector, 0, sizeof(vector));
+ tc_id = json_object_get(test, "tcId");
+ vector.result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector.result == NULL) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ vector.tc_id = json_integer_value(tc_id);
+ if (decode_hex(json_object_get(test, "msg"), &vector.msg,
+ &vector.msg_len) != 0 ||
+ decode_hex(json_object_get(test, "sig"),
+ &sig, &sig_len) != 0) {
+ rte_free(vector.msg);
+ rte_free(sig);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, &vector, "acceptable result policy");
+ rte_free(vector.msg);
+ rte_free(sig);
+ continue;
+ }
+ if (sig_len != 2 * ec.bytesize) {
+ debug_vector("SKIP", name, &vector,
+ "non-canonical P1363 signature size");
+ stats->skipped_unsupported++;
+ rte_free(vector.msg);
+ rte_free(sig);
+ continue;
+ }
+ ret = run_ecdsa_verify(&ec, vector.msg, vector.msg_len, sig, sig_len / 2,
+ sig + sig_len / 2, sig_len / 2, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ } else if (ret == -ETIMEDOUT) {
+ return -ETIMEDOUT;
+ } else if (ret == -EMSGSIZE) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "message length exceeds mbuf");
+ } else if (ret == -EINVAL) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "operand wider than curve");
+ } else if (strcmp(vector.result, "valid") == 0) {
+ if (ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS)
+ stats->passed++;
+ else {
+ debug_vector("FAIL", name, &vector, "valid signature rejected");
+ stats->failed++;
+ }
+ } else if (strcmp(vector.result, "invalid") == 0) {
+ if (ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS) {
+ debug_vector("FAIL", name, &vector, "invalid signature accepted");
+ stats->failed++;
+ } else {
+ stats->passed++;
+ }
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "unknown result");
+ }
+ rte_free(vector.msg);
+ rte_free(sig);
+ }
+ rte_free(ec.wx);
+ rte_free(ec.wy);
+ }
+ return 0;
+}
+
+static int
+process_file(const char *path, struct app_stats *stats)
+{
+ json_error_t error;
+ json_t *root;
+ const char *algorithm;
+ const char *schema;
+ int ret;
+
+ root = json_load_file(path, 0, &error);
+ if (root == NULL) {
+ if (env.debug)
+ printf("SKIP %s: JSON error at line %d: %s\n", path, error.line,
+ error.text);
+ return 0;
+ }
+ algorithm = json_string_value(json_object_get(root, "algorithm"));
+ schema = json_string_value(json_object_get(root, "schema"));
+ if (algorithm == NULL || schema == NULL) {
+ debug_file("SKIP", path, "missing algorithm or schema");
+ stats->skipped_unsupported++;
+ json_decref(root);
+ return 0;
+ }
+ if (strcmp(algorithm, "AES-GCM") == 0 &&
+ strcmp(schema, "aead_test_schema_v1.json") == 0) {
+ printf("Processing AES-GCM vectors: %s\n", path);
+ ret = process_aead(root, RTE_CRYPTO_AEAD_AES_GCM, "AES-GCM", stats);
+ } else if (strcmp(algorithm, "AES-CCM") == 0 &&
+ strcmp(schema, "aead_test_schema_v1.json") == 0) {
+ printf("Processing AES-CCM vectors: %s\n", path);
+ ret = process_aead(root, RTE_CRYPTO_AEAD_AES_CCM, "AES-CCM", stats);
+ } else if (strcmp(algorithm, "CHACHA20-POLY1305") == 0 &&
+ strcmp(schema, "aead_test_schema_v1.json") == 0) {
+ printf("Processing ChaCha20-Poly1305 vectors: %s\n", path);
+ ret = process_aead(root, RTE_CRYPTO_AEAD_CHACHA20_POLY1305,
+ "ChaCha20-Poly1305", stats);
+ } else if (strcmp(algorithm, "SM4-GCM") == 0 &&
+ strcmp(schema, "aead_test_schema_v1.json") == 0) {
+ printf("Processing SM4-GCM vectors: %s\n", path);
+ ret = process_aead(root, RTE_CRYPTO_AEAD_SM4_GCM, "SM4-GCM", stats);
+ } else if (strcmp(algorithm, "HMACSHA256") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA256 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA256_HMAC, "HMAC-SHA256", stats);
+ } else if (strcmp(algorithm, "HMACSHA1") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA1 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA1_HMAC, "HMAC-SHA1", stats);
+ } else if (strcmp(algorithm, "HMACSHA224") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA224 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA224_HMAC, "HMAC-SHA224", stats);
+ } else if (strcmp(algorithm, "HMACSHA384") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA384 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA384_HMAC, "HMAC-SHA384", stats);
+ } else if (strcmp(algorithm, "HMACSHA512") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA512 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA512_HMAC, "HMAC-SHA512", stats);
+ } else if (strcmp(algorithm, "HMACSHA3-224") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA3-224 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA3_224_HMAC, "HMAC-SHA3-224", stats);
+ } else if (strcmp(algorithm, "HMACSHA3-256") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA3-256 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA3_256_HMAC, "HMAC-SHA3-256", stats);
+ } else if (strcmp(algorithm, "HMACSHA3-384") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA3-384 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA3_384_HMAC, "HMAC-SHA3-384", stats);
+ } else if (strcmp(algorithm, "HMACSHA3-512") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA3-512 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA3_512_HMAC, "HMAC-SHA3-512", stats);
+ } else if (strcmp(algorithm, "HMACSM3") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SM3 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SM3_HMAC, "HMAC-SM3", stats);
+ } else if (strcmp(algorithm, "AES-CMAC") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing AES-CMAC vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_AES_CMAC, "AES-CMAC", stats);
+ } else if (strcmp(algorithm, "AES-GMAC") == 0 &&
+ strcmp(schema, "mac_with_iv_test_schema_v1.json") == 0) {
+ printf("Processing AES-GMAC vectors: %s\n", path);
+ ret = process_gmac(root, stats);
+ } else if (strcmp(algorithm, "DSA") == 0 &&
+ strcmp(schema, "dsa_p1363_verify_schema_v1.json") == 0) {
+ printf("Processing DSA (P1363) vectors: %s\n", path);
+ ret = process_dsa_p1363(root, "DSA", stats);
+ } else if (strcmp(algorithm, "ECDH") == 0 &&
+ strcmp(schema, "ecdh_ecpoint_test_schema_v1.json") == 0) {
+ printf("Processing ECDH (ecpoint) vectors: %s\n", path);
+ ret = process_ecdh_ecpoint(root, "ECDH", stats);
+ } else if (strcmp(algorithm, "ECDSA") == 0 &&
+ strcmp(schema, "ecdsa_p1363_verify_schema_v1.json") == 0) {
+ printf("Processing ECDSA (P1363) vectors: %s\n", path);
+ ret = process_ecdsa_p1363(root, "ECDSA", stats);
+ } else if (strncmp(algorithm, "AES-", strlen("AES-")) == 0 ||
+ strncmp(algorithm, "SEED-", strlen("SEED-")) == 0 ||
+ strcmp(algorithm, "SM4-CCM") == 0 ||
+ strncmp(algorithm, "HMAC", strlen("HMAC")) == 0 ||
+ strncmp(algorithm, "RSA", strlen("RSA")) == 0 ||
+ strcmp(algorithm, "DSA") == 0 ||
+ strcmp(algorithm, "ECDH") == 0 ||
+ strcmp(algorithm, "ECDSA") == 0) {
+ debug_file("SKIP", path, algorithm);
+ stats->skipped_unsupported++;
+ ret = 0;
+ } else {
+ debug_file("SKIP", path, "unsupported vector file");
+ stats->skipped_unsupported++;
+ ret = 0;
+ }
+ json_decref(root);
+ return ret;
+}
+
+static int
+process_path(const char *path, struct app_stats *stats)
+{
+ struct stat path_stat;
+ DIR *directory;
+ struct dirent *entry;
+ int ret = 0;
+
+ if (stat(path, &path_stat) != 0)
+ return -errno;
+ if (!S_ISDIR(path_stat.st_mode))
+ return process_file(path, stats);
+
+ directory = opendir(path);
+ if (directory == NULL)
+ return -errno;
+ while ((entry = readdir(directory)) != NULL) {
+ char file_path[PATH_MAX];
+ size_t name_length = strlen(entry->d_name);
+
+ if (name_length < 6 || strcmp(entry->d_name + name_length - 5, ".json") != 0)
+ continue;
+ if (snprintf(file_path, sizeof(file_path), "%s/%s", path, entry->d_name) >=
+ (int)sizeof(file_path)) {
+ ret = -ENAMETOOLONG;
+ break;
+ }
+ ret = process_file(file_path, stats);
+ if (ret != 0)
+ break;
+ }
+ closedir(directory);
+ return ret;
+}
+
+int
+main(int argc, char **argv)
+{
+ struct app_stats stats = { 0 };
+ int eal_args;
+ int ret;
+
+ eal_args = rte_eal_init(argc, argv);
+ if (eal_args < 0)
+ rte_exit(EXIT_FAILURE, "Cannot initialize EAL\n");
+ argc -= eal_args;
+ argv += eal_args;
+
+ ret = parse_args(argc, argv);
+ if (ret != 0) {
+ usage(argv[0]);
+ ret = EXIT_FAILURE;
+ goto out_eal;
+ }
+ ret = app_init();
+ if (ret != 0) {
+ printf("Cannot initialize cryptodev %u: %s\n", env.dev_id, rte_strerror(-ret));
+ ret = EXIT_FAILURE;
+ goto out_eal;
+ }
+
+ ret = process_path(env.vectors_path, &stats);
+ if (ret != 0)
+ printf("Vector processing failed: %s\n", rte_strerror(-ret));
+ printf("Summary: passed=%" PRIu64 " failed=%" PRIu64
+ " skipped_capability=%" PRIu64 " skipped_acceptable=%" PRIu64
+ " skipped_unsupported=%" PRIu64 "\n", stats.passed, stats.failed,
+ stats.skipped_capability, stats.skipped_acceptable, stats.skipped_unsupported);
+ app_uninit();
+ ret = ret == 0 && stats.failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+
+out_eal:
+ rte_eal_cleanup();
+ return ret;
+}
diff --git a/examples/wycheproof_validation/meson.build b/examples/wycheproof_validation/meson.build
new file mode 100644
index 0000000000..7f18b4cef3
--- /dev/null
+++ b/examples/wycheproof_validation/meson.build
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026
+
+# meson file, for building this example as part of a main DPDK build.
+#
+# To build this example as a standalone application with an already-installed
+# DPDK instance, use 'make'
+
+deps += ['cryptodev']
+
+if not dpdk_conf.has('RTE_HAS_JANSSON')
+ build = false
+ message('Skipping wycheproof_validation: Jansson is required')
+else
+ ext_deps += jansson_dep
+ sources = files('main.c')
+endif
--
2.34.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH v5] examples: add Wycheproof validation app
2026-09-22 16:05 ` [PATCH v5] " Kai Ji
@ 2026-09-22 16:26 ` Stephen Hemminger
2026-09-23 10:50 ` [PATCH v6] " Kai Ji
1 sibling, 0 replies; 17+ messages in thread
From: Stephen Hemminger @ 2026-09-22 16:26 UTC (permalink / raw)
To: Kai Ji; +Cc: dev, Thomas Monjalon
On Tue, 22 Sep 2026 16:05:57 +0000
Kai Ji <kai.ji@intel.com> wrote:
> Add a Wycheproof JSON vector validation example for cryptodev PMDs.
>
> Support these algorithms when advertised by the selected PMD:
> - AEAD: AES-GCM, AES-CCM, SM4-GCM, ChaCha20-Poly1305
> - MAC: AES-CMAC, AES-GMAC, HMAC SHA-1/SHA-2/SHA-3/SM3
> - Asymmetric: DSA (P1363 verify), ECDSA (P1363 verify),
> ECDH (ecpoint shared-secret compute)
>
> Validate valid vectors against generated ciphertexts, tags, plaintexts,
> digests, shared secrets, or signature verification, and require the
> expected rejection for invalid vectors. Digest inputs for DSA and ECDSA
> use the symmetric auth path, selecting a separate symmetric-capable
> device when the target device is asymmetric-only.
>
> Skip parameter combinations outside PMD capability ranges and identify
> recognized vector families without a compatible DPDK transform. A
> --debug option lists every failed or skipped vector.
>
> Add Meson and standalone build integration, with usage documentation.
>
> Signed-off-by: Kai Ji <kai.ji@intel.com>
> ---
AI review had some useful suggestions on this.
Wycheproof validation example (v5) - review
v4 -> v5 delta: drop unused APP_NAME, close the device on the
app_init() error path, and treat asym session-create failure as
-ENOTSUP (skip) instead of -EIO/-ENOMEM (fail) in run_dsa_verify(),
run_ecdh_ecpoint() and run_ecdsa_verify().
Applied on 6bbb7b3, built with -Dwerror=true, docutils clean. Same
crypto_openssl runs as v4: directory run passed=1902 failed=0,
--mbuf-dataroom 160 and --cryptodev-id 256 behave as in v4.
No Errors or Warnings.
Info
----
main.c:1222, 1480, 1715
ret = -ENOTSUP;
Session-create failure is now indistinguishable from an unsupported
curve or key size: a PMD that advertises the xform but fails
session setup for a curve it claims to support, or a -ENOMEM from
an exhausted asym session pool, lands in skipped_capability and the
exit status stays zero. Understood that the asym capability struct
does not enumerate curves, so this is a documented trade-off; just
noting that valid asym vectors can no longer fail at session setup.
main.c:823-824, 837, 957, 1047 (open since v3)
memcmp(output, vector->ct, vector->ct_len) != 0
memcmp(NULL, NULL, 0) for empty-message vectors; glibc declares
memcmp nonnull. Guard with len != 0 &&.
main.c:182-183, 266 (open since v3)
Positional struct initializers; use designated members.
doc/guides/sample_app_ug/wycheproof_validation.rst (open since v3)
crypto_openssl advertises no ECDSA/ECDH xform capability, so the
documented command line skips every ECDSA/ECDH vector. One sentence
saying asymmetric coverage needs a PMD advertising those xforms.
Pre-existing, not introduced by this patch
lib/cryptodev/rte_cryptodev.c:2411 rte_cryptodev_asym_session_create()
ret = dev->dev_ops->asym_session_configure(dev, xforms, sess);
if (ret < 0) {
...
return ret;
}
On configure failure the object stays allocated from the pool and
*session still points at it, unlike the sym variant which does
rte_mempool_put() on its error_exit path. This app copes by freeing
a non-NULL session at out: even when create returned an error; if
the library is ever aligned with the sym behaviour that becomes a
double put. One for the cryptodev maintainers, not this patch.
Review-Result: CLEAN
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v6] examples: add Wycheproof validation app
2026-09-22 16:05 ` [PATCH v5] " Kai Ji
2026-09-22 16:26 ` Stephen Hemminger
@ 2026-09-23 10:50 ` Kai Ji
2026-09-23 15:04 ` [PATCH v7] " Kai Ji
1 sibling, 1 reply; 17+ messages in thread
From: Kai Ji @ 2026-09-23 10:50 UTC (permalink / raw)
To: dev; +Cc: Kai Ji, Thomas Monjalon
Add a Wycheproof JSON vector validation example for cryptodev PMDs.
Support these algorithms when advertised by the selected PMD:
- AEAD: AES-GCM, AES-CCM, SM4-GCM, ChaCha20-Poly1305
- MAC: AES-CMAC, AES-GMAC, HMAC SHA-1/SHA-2/SHA-3/SM3
- Asymmetric: DSA (P1363 verify), ECDSA (P1363 verify),
ECDH (ecpoint shared-secret compute)
Validate valid vectors against generated ciphertexts, tags, plaintexts,
digests, shared secrets, or signature verification, and require the
expected rejection for invalid vectors. Digest inputs for DSA and ECDSA
use the symmetric auth path, selecting a separate symmetric-capable
device when the target device is asymmetric-only.
Skip parameter combinations outside PMD capability ranges and identify
recognized vector families without a compatible DPDK transform. A
--debug option lists every failed or skipped vector.
Add Meson and standalone build integration, with usage documentation.
Signed-off-by: Kai Ji <kai.ji@intel.com>
---
MAINTAINERS | 5 +
doc/guides/rel_notes/release_26_11.rst | 6 +
doc/guides/sample_app_ug/index.rst | 1 +
.../sample_app_ug/wycheproof_validation.rst | 50 +
examples/meson.build | 1 +
examples/wycheproof_validation/Makefile | 40 +
examples/wycheproof_validation/main.c | 2090 +++++++++++++++++
examples/wycheproof_validation/meson.build | 17 +
8 files changed, 2210 insertions(+)
create mode 100644 doc/guides/sample_app_ug/wycheproof_validation.rst
create mode 100644 examples/wycheproof_validation/Makefile
create mode 100644 examples/wycheproof_validation/main.c
create mode 100644 examples/wycheproof_validation/meson.build
diff --git a/MAINTAINERS b/MAINTAINERS
index 8c50c52933..d33923cbb2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2106,3 +2106,8 @@ F: examples/vmdq/
F: doc/guides/sample_app_ug/vmdq_forwarding.rst
F: examples/vmdq_dcb/
F: doc/guides/sample_app_ug/vmdq_dcb_forwarding.rst
+
+Wycheproof validation example
+M: Kai Ji <kai.ji@intel.com>
+F: examples/wycheproof_validation/
+F: doc/guides/sample_app_ug/wycheproof_validation.rst
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 4b3e5d995c..0126d25a58 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -55,6 +55,12 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **Added Wycheproof validation example application.**
+
+ Added a new example application that validates a DPDK cryptodev PMD against
+ the Google Wycheproof JSON test vectors, covering AEAD, MAC, DSA, ECDH and
+ ECDSA algorithm families.
+
Removed Items
-------------
diff --git a/doc/guides/sample_app_ug/index.rst b/doc/guides/sample_app_ug/index.rst
index f12623bb66..29313fe3de 100644
--- a/doc/guides/sample_app_ug/index.rst
+++ b/doc/guides/sample_app_ug/index.rst
@@ -53,6 +53,7 @@ Sample Applications User Guides
ptpclient
ptp_tap_relay_sw
fips_validation
+ wycheproof_validation
ipsec_secgw
bbdev_app
ntb
diff --git a/doc/guides/sample_app_ug/wycheproof_validation.rst b/doc/guides/sample_app_ug/wycheproof_validation.rst
new file mode 100644
index 0000000000..17c8bd2b55
--- /dev/null
+++ b/doc/guides/sample_app_ug/wycheproof_validation.rst
@@ -0,0 +1,50 @@
+.. SPDX-License-Identifier: BSD-3-Clause
+ Copyright(c) 2026 Intel Corporation.
+
+Wycheproof Validation Example
+=============================
+
+Overview
+--------
+
+This example validates a DPDK cryptodev implementation against the Google
+Wycheproof JSON test vectors.
+
+The application reads one JSON file or a directory of JSON files at runtime and
+checks the supported algorithm families against the selected PMD. It can be used
+with a PMD that advertises AEAD, MAC, DSA, ECDH, or ECDSA support.
+
+Build
+-----
+
+Build the example from the DPDK tree with Meson:
+
+.. code-block:: console
+
+ meson setup build -Dexamples=wycheproof_validation -Denable_drivers=crypto/openssl
+ meson compile -C build
+
+Standalone Makefile builds are also supported from the example directory.
+
+Run
+---
+
+Run the example with an OpenSSL-backed cryptodev and a vector file:
+
+.. code-block:: console
+
+ ./build/examples/dpdk-wycheproof_validation --vdev crypto_openssl -- \
+ --vectors ../wycheproof/testvectors_v1/aes_gcm_test.json \
+ --cryptodev crypto_openssl --debug
+
+The program accepts a single JSON file or a directory of JSON files. Use
+``--debug`` to print each failed or skipped vector with its reason.
+
+Asymmetric coverage (DSA, ECDH, ECDSA) requires a PMD that advertises the
+corresponding asymmetric xform capabilities. The ``crypto_openssl`` PMD used
+above does not advertise the ECDSA or ECDH xforms, so those vectors are skipped;
+select a PMD that advertises them to exercise the asymmetric families.
+
+The exit code is nonzero when a parsed and supported vector fails validation;
+unsupported vectors, capability-skipped cases, and acceptable results are reported
+in the summary without forcing a failure.
diff --git a/examples/meson.build b/examples/meson.build
index 25d9c88457..000d05863a 100644
--- a/examples/meson.build
+++ b/examples/meson.build
@@ -59,6 +59,7 @@ all_examples = [
'vm_power_manager/guest_cli',
'vmdq',
'vmdq_dcb',
+ 'wycheproof_validation',
]
# on install, skip copying all meson.build files
diff --git a/examples/wycheproof_validation/Makefile b/examples/wycheproof_validation/Makefile
new file mode 100644
index 0000000000..8cc0293364
--- /dev/null
+++ b/examples/wycheproof_validation/Makefile
@@ -0,0 +1,40 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026
+
+APP = wycheproof_validation
+SRCS-y := main.c
+
+PKGCONF ?= pkg-config
+
+ifneq ($(shell $(PKGCONF) --exists libdpdk && echo 0),0)
+$(error "no installation of DPDK found")
+endif
+ifneq ($(shell $(PKGCONF) --exists jansson && echo 0),0)
+$(error "Jansson is required")
+endif
+
+all: shared
+.PHONY: shared static
+shared: build/$(APP)-shared
+ ln -sf $(APP)-shared build/$(APP)
+static: build/$(APP)-static
+ ln -sf $(APP)-static build/$(APP)
+
+PC_FILE := $(shell $(PKGCONF) --path libdpdk 2>/dev/null)
+CFLAGS += -O3 $(shell $(PKGCONF) --cflags libdpdk jansson)
+LDFLAGS_SHARED = $(shell $(PKGCONF) --libs libdpdk jansson)
+LDFLAGS_STATIC = $(shell $(PKGCONF) --static --libs libdpdk jansson)
+
+build/$(APP)-shared: $(SRCS-y) Makefile $(PC_FILE) | build
+ $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_SHARED)
+
+build/$(APP)-static: $(SRCS-y) Makefile $(PC_FILE) | build
+ $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_STATIC)
+
+build:
+ @mkdir -p $@
+
+.PHONY: clean
+clean:
+ rm -f build/$(APP) build/$(APP)-static build/$(APP)-shared
+ test -d build && rmdir -p build || true
diff --git a/examples/wycheproof_validation/main.c b/examples/wycheproof_validation/main.c
new file mode 100644
index 0000000000..d22d26e949
--- /dev/null
+++ b/examples/wycheproof_validation/main.c
@@ -0,0 +1,2090 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026
+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <jansson.h>
+
+#include <rte_cryptodev.h>
+#include <rte_cycles.h>
+#include <rte_eal.h>
+#include <rte_errno.h>
+#include <rte_mbuf.h>
+#include <rte_mempool.h>
+#include <rte_malloc.h>
+
+#define DEFAULT_MBUF_DATA_ROOM 16384
+#define NUM_QP_DESCRIPTORS 128
+#define MAX_IV_LEN 512
+#define MAX_EC_BYTESIZE 66
+/* CCM AAD is prefixed with an 18-byte reserved area (B0 block + length). */
+#define AES_CCM_AAD_OFFSET 18
+#define IV_OFFSET (sizeof(struct rte_crypto_op) + sizeof(struct rte_crypto_sym_op))
+
+struct wycheproof_data {
+ uint8_t *key;
+ uint8_t *iv;
+ uint8_t *aad;
+ uint8_t *msg;
+ uint8_t *ct;
+ uint8_t *tag;
+ uint32_t key_len;
+ uint32_t iv_len;
+ uint32_t aad_len;
+ uint32_t msg_len;
+ uint32_t ct_len;
+ uint32_t tag_len;
+ json_int_t tc_id;
+ const char *result;
+};
+
+struct app_env {
+ uint8_t dev_id;
+ uint8_t hash_dev_id;
+ bool hash_dev_own;
+ uint32_t mbuf_data_room;
+ const char *vectors_path;
+ bool debug;
+ struct rte_mempool *mbuf_pool;
+ struct rte_mempool *session_pool;
+ struct rte_mempool *op_pool;
+ struct rte_mempool *asym_session_pool;
+ struct rte_mempool *asym_op_pool;
+};
+
+struct app_stats {
+ uint64_t passed;
+ uint64_t failed;
+ uint64_t skipped_capability;
+ uint64_t skipped_acceptable;
+ uint64_t skipped_unsupported;
+};
+
+static struct app_env env = {
+ .mbuf_data_room = DEFAULT_MBUF_DATA_ROOM,
+};
+
+static void
+usage(const char *program)
+{
+ printf("%s [EAL options] -- "
+ "--vectors PATH [--cryptodev NAME | --cryptodev-id ID] [--debug]\n",
+ program);
+}
+
+static void
+debug_vector(const char *type, const char *algorithm,
+ const struct wycheproof_data *vector, const char *reason)
+{
+ if (env.debug)
+ printf("%s %s tcId=%" JSON_INTEGER_FORMAT " result=%s: %s\n", type,
+ algorithm, vector->tc_id, vector->result, reason);
+}
+
+static void
+debug_file(const char *type, const char *path, const char *reason)
+{
+ if (env.debug)
+ printf("%s %s: %s\n", type, path, reason);
+}
+
+static int
+parse_uint32(const char *text, uint32_t *value)
+{
+ char *end;
+ unsigned long parsed;
+
+ errno = 0;
+ parsed = strtoul(text, &end, 10);
+ if (errno != 0 || *text == '\0' || *end != '\0' || parsed > UINT32_MAX)
+ return -EINVAL;
+
+ *value = parsed;
+ return 0;
+}
+
+static int
+parse_args(int argc, char **argv)
+{
+ static const struct option options[] = {
+ { "vectors", required_argument, NULL, 'v' },
+ { "cryptodev", required_argument, NULL, 'c' },
+ { "cryptodev-id", required_argument, NULL, 'd' },
+ { "mbuf-dataroom", required_argument, NULL, 'm' },
+ { "debug", no_argument, NULL, 'D' },
+ { NULL, 0, NULL, 0 },
+ };
+ bool have_device = false;
+ int option;
+
+ if (rte_cryptodev_count() == 0)
+ return -ENODEV;
+ env.dev_id = 0;
+
+ while ((option = getopt_long(argc, argv, "v:c:d:m:D", options, NULL)) != -1) {
+ uint32_t value;
+ int dev_id;
+
+ switch (option) {
+ case 'v':
+ env.vectors_path = optarg;
+ break;
+ case 'c':
+ dev_id = rte_cryptodev_get_dev_id(optarg);
+ if (dev_id < 0)
+ return dev_id;
+ env.dev_id = dev_id;
+ have_device = true;
+ break;
+ case 'd':
+ if (parse_uint32(optarg, &value) != 0 || value > UINT8_MAX ||
+ !rte_cryptodev_is_valid_dev(value))
+ return -EINVAL;
+ env.dev_id = value;
+ have_device = true;
+ break;
+ case 'm':
+ if (parse_uint32(optarg, &env.mbuf_data_room) != 0 ||
+ env.mbuf_data_room < RTE_PKTMBUF_HEADROOM ||
+ env.mbuf_data_room > UINT16_MAX)
+ return -EINVAL;
+ break;
+ case 'D':
+ env.debug = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ if (env.vectors_path == NULL)
+ return -EINVAL;
+ if (!have_device)
+ printf("Using cryptodev %u (%s)\n", env.dev_id,
+ rte_cryptodev_name_get(env.dev_id));
+
+ return 0;
+}
+
+static int
+app_init(void)
+{
+ struct rte_cryptodev_config config = {
+ .socket_id = rte_socket_id(),
+ .nb_queue_pairs = 1,
+ .ff_disable = 0,
+ };
+ struct rte_cryptodev_qp_conf queue_pair = {
+ .nb_descriptors = NUM_QP_DESCRIPTORS,
+ .mp_session = NULL,
+ };
+ struct rte_cryptodev_info dev_info;
+ uint32_t session_size;
+ int ret;
+
+ rte_cryptodev_info_get(env.dev_id, &dev_info);
+ if (dev_info.max_nb_queue_pairs < config.nb_queue_pairs)
+ return -ENOTSUP;
+
+ ret = rte_cryptodev_configure(env.dev_id, &config);
+ if (ret < 0)
+ return ret;
+
+ env.mbuf_pool = rte_pktmbuf_pool_create("WYCHEPROOF_MBUF_POOL", 64, 0, 0,
+ env.mbuf_data_room, rte_socket_id());
+ if (env.mbuf_pool == NULL) {
+ ret = -rte_errno;
+ goto error;
+ }
+
+ session_size = rte_cryptodev_sym_get_private_session_size(env.dev_id);
+ {
+ /* The hashing device may differ from the target device, so size the
+ * session pool for the largest sym session across all devices.
+ */
+ uint8_t d;
+
+ for (d = 0; d < rte_cryptodev_count(); d++) {
+ uint32_t s = rte_cryptodev_sym_get_private_session_size(d);
+
+ if (s > session_size)
+ session_size = s;
+ }
+ }
+ env.session_pool = rte_cryptodev_sym_session_pool_create("WYCHEPROOF_SESSION_POOL",
+ 64, session_size, 0, 0, rte_socket_id());
+ if (env.session_pool == NULL) {
+ ret = -rte_errno;
+ goto error;
+ }
+
+ /* priv_size is measured from the end of the sym op; the IV lives there. */
+ env.op_pool = rte_crypto_op_pool_create("WYCHEPROOF_OP_POOL",
+ RTE_CRYPTO_OP_TYPE_SYMMETRIC, 64, 0, MAX_IV_LEN, rte_socket_id());
+ if (env.op_pool == NULL) {
+ ret = -rte_errno;
+ goto error;
+ }
+
+ /* Asymmetric pools are best-effort; DSA/ECDSA paths gate on the
+ * device asymmetric feature flag at runtime.
+ */
+ env.asym_session_pool = rte_cryptodev_asym_session_pool_create(
+ "WYCHEPROOF_ASYM_SESS", 16, 0, 0, rte_socket_id());
+ env.asym_op_pool = rte_crypto_op_pool_create("WYCHEPROOF_ASYM_OP",
+ RTE_CRYPTO_OP_TYPE_ASYMMETRIC, 64, 0, 0, rte_socket_id());
+
+ queue_pair.mp_session = env.session_pool;
+ ret = rte_cryptodev_queue_pair_setup(env.dev_id, 0, &queue_pair,
+ rte_socket_id());
+ if (ret < 0)
+ goto error;
+
+ ret = rte_cryptodev_start(env.dev_id);
+ if (ret < 0)
+ goto error;
+
+ /* Digest computation for DSA/ECDSA needs a symmetric-auth device. The
+ * target device may be asym-only (for example QAT), so pick a separate
+ * sym-capable device for hashing when needed.
+ */
+ env.hash_dev_id = env.dev_id;
+ env.hash_dev_own = false;
+ {
+ struct rte_cryptodev_sym_capability_idx hash_idx = {
+ .type = RTE_CRYPTO_SYM_XFORM_AUTH,
+ .algo.auth = RTE_CRYPTO_AUTH_SHA256,
+ };
+ uint8_t count = rte_cryptodev_count();
+ uint8_t d;
+
+ if (rte_cryptodev_sym_capability_get(env.dev_id, &hash_idx) == NULL) {
+ for (d = 0; d < count; d++) {
+ struct rte_cryptodev_qp_conf hash_qp = {
+ .nb_descriptors = NUM_QP_DESCRIPTORS,
+ .mp_session = NULL,
+ };
+
+ if (d == env.dev_id)
+ continue;
+ if (rte_cryptodev_sym_capability_get(d, &hash_idx) == NULL)
+ continue;
+ if (rte_cryptodev_configure(d, &config) < 0)
+ continue;
+ hash_qp.mp_session = env.session_pool;
+ if (rte_cryptodev_queue_pair_setup(d, 0, &hash_qp,
+ rte_socket_id()) < 0 ||
+ rte_cryptodev_start(d) < 0) {
+ rte_cryptodev_close(d);
+ continue;
+ }
+ env.hash_dev_id = d;
+ env.hash_dev_own = true;
+ break;
+ }
+ }
+ }
+
+ return 0;
+
+error:
+ rte_mempool_free(env.asym_op_pool);
+ rte_mempool_free(env.asym_session_pool);
+ rte_mempool_free(env.op_pool);
+ rte_mempool_free(env.session_pool);
+ rte_mempool_free(env.mbuf_pool);
+ env.asym_op_pool = NULL;
+ env.asym_session_pool = NULL;
+ env.op_pool = NULL;
+ env.session_pool = NULL;
+ env.mbuf_pool = NULL;
+ rte_cryptodev_close(env.dev_id);
+ return ret;
+}
+
+static void
+app_uninit(void)
+{
+ if (env.hash_dev_own) {
+ rte_cryptodev_stop(env.hash_dev_id);
+ rte_cryptodev_close(env.hash_dev_id);
+ }
+ rte_cryptodev_stop(env.dev_id);
+ rte_cryptodev_close(env.dev_id);
+ rte_mempool_free(env.asym_op_pool);
+ rte_mempool_free(env.asym_session_pool);
+ rte_mempool_free(env.op_pool);
+ rte_mempool_free(env.session_pool);
+ rte_mempool_free(env.mbuf_pool);
+}
+
+static void
+free_vector(struct wycheproof_data *vector)
+{
+ rte_free(vector->key);
+ rte_free(vector->iv);
+ rte_free(vector->aad);
+ rte_free(vector->msg);
+ rte_free(vector->ct);
+ rte_free(vector->tag);
+ memset(vector, 0, sizeof(*vector));
+}
+
+static int
+hex_value(char character)
+{
+ if (character >= '0' && character <= '9')
+ return character - '0';
+ if (character >= 'a' && character <= 'f')
+ return character - 'a' + 10;
+ if (character >= 'A' && character <= 'F')
+ return character - 'A' + 10;
+ return -1;
+}
+
+static int
+decode_hex(json_t *value, uint8_t **buffer, uint32_t *length)
+{
+ const char *hex;
+ size_t hex_length;
+ uint8_t *decoded;
+ size_t index;
+
+ if (!json_is_string(value))
+ return -EINVAL;
+
+ hex = json_string_value(value);
+ hex_length = strlen(hex);
+ if ((hex_length & 1) != 0 || hex_length / 2 > UINT32_MAX)
+ return -EINVAL;
+
+ *length = hex_length / 2;
+ if (*length == 0)
+ return 0;
+
+ decoded = rte_malloc(NULL, *length, 0);
+ if (decoded == NULL)
+ return -ENOMEM;
+
+ for (index = 0; index < *length; index++) {
+ int high = hex_value(hex[index * 2]);
+ int low = hex_value(hex[index * 2 + 1]);
+
+ if (high < 0 || low < 0) {
+ rte_free(decoded);
+ return -EINVAL;
+ }
+ decoded[index] = (high << 4) | low;
+ }
+
+ *buffer = decoded;
+ return 0;
+}
+
+static int
+parse_vector(json_t *test, struct wycheproof_data *vector)
+{
+ json_t *tc_id;
+ int ret;
+
+ memset(vector, 0, sizeof(*vector));
+ tc_id = json_object_get(test, "tcId");
+ vector->result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector->result == NULL)
+ return -EINVAL;
+ vector->tc_id = json_integer_value(tc_id);
+
+ ret = decode_hex(json_object_get(test, "key"), &vector->key, &vector->key_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "iv"), &vector->iv, &vector->iv_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "aad"), &vector->aad, &vector->aad_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "msg"), &vector->msg, &vector->msg_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "ct"), &vector->ct, &vector->ct_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "tag"), &vector->tag, &vector->tag_len);
+ if (ret != 0)
+ goto error;
+
+ return 0;
+
+error:
+ free_vector(vector);
+ return ret;
+}
+
+static int
+parse_mac_vector(json_t *test, struct wycheproof_data *vector)
+{
+ json_t *tc_id;
+ int ret;
+
+ memset(vector, 0, sizeof(*vector));
+ tc_id = json_object_get(test, "tcId");
+ vector->result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector->result == NULL)
+ return -EINVAL;
+ vector->tc_id = json_integer_value(tc_id);
+
+ ret = decode_hex(json_object_get(test, "key"), &vector->key, &vector->key_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "msg"), &vector->msg, &vector->msg_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "tag"), &vector->tag, &vector->tag_len);
+ if (ret != 0)
+ goto error;
+
+ return 0;
+
+error:
+ free_vector(vector);
+ return ret;
+}
+
+static int
+parse_mac_with_iv_vector(json_t *test, struct wycheproof_data *vector)
+{
+ int ret;
+
+ ret = parse_mac_vector(test, vector);
+ if (ret != 0)
+ return ret;
+ ret = decode_hex(json_object_get(test, "iv"), &vector->iv, &vector->iv_len);
+ if (ret != 0)
+ free_vector(vector);
+ return ret;
+}
+
+/* Bounded wait for one completed op; NULL on timeout so a stuck PMD fails the
+ * vector instead of hanging the tool.
+ */
+static struct rte_crypto_op *
+dequeue_one(uint8_t dev_id)
+{
+ struct rte_crypto_op *completed = NULL;
+ uint64_t deadline = rte_get_timer_cycles() + rte_get_timer_hz();
+
+ while (rte_cryptodev_dequeue_burst(dev_id, 0, &completed, 1) == 0) {
+ if (rte_get_timer_cycles() > deadline)
+ return NULL;
+ rte_pause();
+ }
+ return completed;
+}
+
+static int
+check_aead_capability(const struct wycheproof_data *vector,
+ enum rte_crypto_aead_algorithm algorithm)
+{
+ struct rte_cryptodev_sym_capability_idx index = {
+ .type = RTE_CRYPTO_SYM_XFORM_AEAD,
+ .algo.aead = algorithm,
+ };
+ const struct rte_cryptodev_symmetric_capability *capability;
+
+ capability = rte_cryptodev_sym_capability_get(env.dev_id, &index);
+ if (capability == NULL)
+ return -ENOTSUP;
+
+ return rte_cryptodev_sym_capability_check_aead(capability, vector->key_len,
+ vector->tag_len, vector->aad_len, vector->iv_len);
+}
+
+static int
+run_aead(const struct wycheproof_data *vector, enum rte_crypto_aead_algorithm algorithm,
+ enum rte_crypto_aead_operation operation, uint8_t **output, uint8_t **digest,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_sym_xform xform = { 0 };
+ struct rte_cryptodev_sym_session *session = NULL;
+ struct rte_crypto_op *operation_op = NULL;
+ struct rte_mbuf *mbuf = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_crypto_sym_op *sym_op;
+ uint8_t *aad = NULL;
+ uint8_t *input;
+ uint32_t input_len;
+ int ret = -ENOMEM;
+
+ *output = NULL;
+ *digest = NULL;
+ xform.type = RTE_CRYPTO_SYM_XFORM_AEAD;
+ xform.aead.algo = algorithm;
+ xform.aead.op = operation;
+ xform.aead.key.data = vector->key;
+ xform.aead.key.length = vector->key_len;
+ xform.aead.iv.offset = IV_OFFSET;
+ xform.aead.iv.length = vector->iv_len;
+ xform.aead.aad_length = vector->aad_len;
+ xform.aead.digest_length = vector->tag_len;
+
+ session = rte_cryptodev_sym_session_create(env.dev_id, &xform, env.session_pool);
+ if (session == NULL)
+ goto out;
+
+ operation_op = rte_crypto_op_alloc(env.op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC);
+ if (operation_op == NULL)
+ goto out;
+
+ mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
+ if (mbuf == NULL)
+ goto out;
+ input = operation == RTE_CRYPTO_AEAD_OP_ENCRYPT ? vector->msg : vector->ct;
+ input_len = operation == RTE_CRYPTO_AEAD_OP_ENCRYPT ? vector->msg_len : vector->ct_len;
+ if (input_len > rte_pktmbuf_tailroom(mbuf)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ if (input_len != 0)
+ memcpy(rte_pktmbuf_append(mbuf, input_len), input, input_len);
+
+ *digest = rte_malloc(NULL, vector->tag_len, RTE_CACHE_LINE_SIZE);
+ if (*digest == NULL && vector->tag_len != 0)
+ goto out;
+ if (operation == RTE_CRYPTO_AEAD_OP_DECRYPT && vector->tag_len != 0)
+ memcpy(*digest, vector->tag, vector->tag_len);
+
+ sym_op = operation_op->sym;
+ sym_op->m_src = mbuf;
+ sym_op->aead.data.offset = 0;
+ sym_op->aead.data.length = input_len;
+ if (algorithm == RTE_CRYPTO_AEAD_AES_CCM) {
+ aad = rte_zmalloc(NULL,
+ RTE_ALIGN_CEIL(vector->aad_len + AES_CCM_AAD_OFFSET, 16), 0);
+ if (aad == NULL)
+ goto out;
+ if (vector->aad_len != 0)
+ memcpy(aad + AES_CCM_AAD_OFFSET, vector->aad, vector->aad_len);
+ sym_op->aead.aad.data = aad;
+ sym_op->aead.aad.phys_addr = rte_malloc_virt2iova(aad);
+ memcpy(rte_crypto_op_ctod_offset(operation_op, uint8_t *, IV_OFFSET) + 1,
+ vector->iv, vector->iv_len);
+ } else {
+ sym_op->aead.aad.data = vector->aad;
+ sym_op->aead.aad.phys_addr = rte_malloc_virt2iova(vector->aad);
+ memcpy(rte_crypto_op_ctod_offset(operation_op, uint8_t *, IV_OFFSET), vector->iv,
+ vector->iv_len);
+ }
+ sym_op->aead.digest.data = *digest;
+ sym_op->aead.digest.phys_addr = rte_malloc_virt2iova(*digest);
+ rte_crypto_op_attach_sym_session(operation_op, session);
+
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &operation_op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+
+ *status = completed->status;
+ if (*status == RTE_CRYPTO_OP_STATUS_SUCCESS && input_len != 0) {
+ *output = rte_malloc(NULL, input_len, 0);
+ if (*output == NULL)
+ goto out;
+ memcpy(*output, rte_pktmbuf_mtod(mbuf, uint8_t *), input_len);
+ }
+ ret = 0;
+
+out:
+ rte_free(aad);
+ rte_pktmbuf_free(mbuf);
+ rte_crypto_op_free(operation_op);
+ rte_cryptodev_sym_session_free(env.dev_id, session);
+ if (ret != 0) {
+ rte_free(*output);
+ rte_free(*digest);
+ *output = NULL;
+ *digest = NULL;
+ }
+ return ret;
+}
+
+static int
+run_hmac(const struct wycheproof_data *vector, enum rte_crypto_auth_algorithm algorithm,
+ enum rte_crypto_auth_operation operation, uint8_t **digest,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_sym_xform xform = { 0 };
+ struct rte_cryptodev_sym_session *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_mbuf *mbuf = NULL;
+ const struct rte_cryptodev_symmetric_capability *capability;
+ struct rte_cryptodev_sym_capability_idx index = {
+ .type = RTE_CRYPTO_SYM_XFORM_AUTH,
+ .algo.auth = algorithm,
+ };
+ int ret = -ENOMEM;
+
+ *digest = NULL;
+ capability = rte_cryptodev_sym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_sym_capability_check_auth(capability,
+ vector->key_len, vector->tag_len, 0) != 0)
+ return -ENOTSUP;
+
+ xform.type = RTE_CRYPTO_SYM_XFORM_AUTH;
+ xform.auth.algo = algorithm;
+ xform.auth.op = operation;
+ xform.auth.key.data = vector->key;
+ xform.auth.key.length = vector->key_len;
+ xform.auth.digest_length = vector->tag_len;
+ session = rte_cryptodev_sym_session_create(env.dev_id, &xform, env.session_pool);
+ if (session == NULL)
+ goto out;
+
+ op = rte_crypto_op_alloc(env.op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC);
+ if (op == NULL)
+ goto out;
+ mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
+ if (mbuf == NULL)
+ goto out;
+ if (vector->msg_len > rte_pktmbuf_tailroom(mbuf)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ if (vector->msg_len != 0)
+ memcpy(rte_pktmbuf_append(mbuf, vector->msg_len), vector->msg, vector->msg_len);
+
+ *digest = rte_malloc(NULL, vector->tag_len, RTE_CACHE_LINE_SIZE);
+ if (*digest == NULL && vector->tag_len != 0)
+ goto out;
+ if (operation == RTE_CRYPTO_AUTH_OP_VERIFY && vector->tag_len != 0)
+ memcpy(*digest, vector->tag, vector->tag_len);
+
+ op->sym->m_src = mbuf;
+ op->sym->auth.data.offset = 0;
+ op->sym->auth.data.length = vector->msg_len;
+ op->sym->auth.digest.data = *digest;
+ op->sym->auth.digest.phys_addr = rte_malloc_virt2iova(*digest);
+ rte_crypto_op_attach_sym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+ *status = completed->status;
+ ret = 0;
+
+out:
+ rte_pktmbuf_free(mbuf);
+ rte_crypto_op_free(op);
+ rte_cryptodev_sym_session_free(env.dev_id, session);
+ if (ret != 0) {
+ rte_free(*digest);
+ *digest = NULL;
+ }
+ return ret;
+}
+
+static int
+run_gmac(const struct wycheproof_data *vector, enum rte_crypto_auth_operation operation,
+ uint8_t **digest, enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_sym_xform xform = { 0 };
+ struct rte_cryptodev_sym_session *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_mbuf *mbuf = NULL;
+ const struct rte_cryptodev_symmetric_capability *capability;
+ struct rte_cryptodev_sym_capability_idx index = {
+ .type = RTE_CRYPTO_SYM_XFORM_AUTH,
+ .algo.auth = RTE_CRYPTO_AUTH_AES_GMAC,
+ };
+ int ret = -ENOMEM;
+
+ *digest = NULL;
+ capability = rte_cryptodev_sym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_sym_capability_check_auth(capability,
+ vector->key_len, vector->tag_len, vector->iv_len) != 0)
+ return -ENOTSUP;
+
+ xform.type = RTE_CRYPTO_SYM_XFORM_AUTH;
+ xform.auth.algo = RTE_CRYPTO_AUTH_AES_GMAC;
+ xform.auth.op = operation;
+ xform.auth.key.data = vector->key;
+ xform.auth.key.length = vector->key_len;
+ xform.auth.iv.offset = IV_OFFSET;
+ xform.auth.iv.length = vector->iv_len;
+ xform.auth.digest_length = vector->tag_len;
+ session = rte_cryptodev_sym_session_create(env.dev_id, &xform, env.session_pool);
+ if (session == NULL)
+ goto out;
+
+ op = rte_crypto_op_alloc(env.op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC);
+ if (op == NULL)
+ goto out;
+ mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
+ if (mbuf == NULL)
+ goto out;
+ if (vector->msg_len > rte_pktmbuf_tailroom(mbuf)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ if (vector->msg_len != 0)
+ memcpy(rte_pktmbuf_append(mbuf, vector->msg_len), vector->msg, vector->msg_len);
+
+ *digest = rte_malloc(NULL, vector->tag_len, RTE_CACHE_LINE_SIZE);
+ if (*digest == NULL && vector->tag_len != 0)
+ goto out;
+ if (operation == RTE_CRYPTO_AUTH_OP_VERIFY && vector->tag_len != 0)
+ memcpy(*digest, vector->tag, vector->tag_len);
+
+ op->sym->m_src = mbuf;
+ op->sym->auth.data.offset = 0;
+ op->sym->auth.data.length = vector->msg_len;
+ op->sym->auth.digest.data = *digest;
+ op->sym->auth.digest.phys_addr = rte_malloc_virt2iova(*digest);
+ memcpy(rte_crypto_op_ctod_offset(op, uint8_t *, IV_OFFSET), vector->iv, vector->iv_len);
+ rte_crypto_op_attach_sym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+ *status = completed->status;
+ ret = 0;
+
+out:
+ rte_pktmbuf_free(mbuf);
+ rte_crypto_op_free(op);
+ rte_cryptodev_sym_session_free(env.dev_id, session);
+ if (ret != 0) {
+ rte_free(*digest);
+ *digest = NULL;
+ }
+ return ret;
+}
+
+static int
+validate_aead_vector(const struct wycheproof_data *vector,
+ enum rte_crypto_aead_algorithm algorithm, const char *name,
+ struct app_stats *stats)
+{
+ uint8_t *output = NULL;
+ uint8_t *digest = NULL;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ int ret;
+
+ if (strcmp(vector->result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, vector, "acceptable result policy");
+ return 0;
+ }
+ if (strcmp(vector->result, "valid") != 0 && strcmp(vector->result, "invalid") != 0) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, vector, "unknown result");
+ return 0;
+ }
+ /* CCM writes the IV one byte into the reserved area (after the flags byte). */
+ if (vector->iv_len + (algorithm == RTE_CRYPTO_AEAD_AES_CCM ? 1u : 0u) > MAX_IV_LEN ||
+ vector->msg_len != vector->ct_len ||
+ vector->msg_len > env.mbuf_data_room - RTE_PKTMBUF_HEADROOM) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, vector, "unsupported message length");
+ return 0;
+ }
+ if (check_aead_capability(vector, algorithm) != 0) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, vector, "PMD capability");
+ return 0;
+ }
+
+ if (strcmp(vector->result, "valid") == 0) {
+ ret = run_aead(vector, algorithm, RTE_CRYPTO_AEAD_OP_ENCRYPT, &output, &digest,
+ &status);
+ if (ret == -ETIMEDOUT)
+ return -ETIMEDOUT;
+ if (ret == -EMSGSIZE)
+ goto unsupported;
+ if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS ||
+ (vector->ct_len != 0 &&
+ memcmp(output, vector->ct, vector->ct_len) != 0) ||
+ (vector->tag_len != 0 &&
+ memcmp(digest, vector->tag, vector->tag_len) != 0))
+ goto failed;
+ rte_free(output);
+ rte_free(digest);
+ output = NULL;
+ digest = NULL;
+ ret = run_aead(vector, algorithm, RTE_CRYPTO_AEAD_OP_DECRYPT, &output, &digest,
+ &status);
+ if (ret == -ETIMEDOUT)
+ return -ETIMEDOUT;
+ if (ret == -EMSGSIZE)
+ goto unsupported;
+ if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS ||
+ (vector->msg_len != 0 &&
+ memcmp(output, vector->msg, vector->msg_len) != 0))
+ goto failed;
+ } else {
+ ret = run_aead(vector, algorithm, RTE_CRYPTO_AEAD_OP_DECRYPT, &output, &digest,
+ &status);
+ if (ret == -ETIMEDOUT)
+ return -ETIMEDOUT;
+ if (ret == -EMSGSIZE)
+ goto unsupported;
+ if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_AUTH_FAILED)
+ goto failed;
+ }
+
+ stats->passed++;
+ rte_free(output);
+ rte_free(digest);
+ return 0;
+
+unsupported:
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, vector, "message length exceeds mbuf");
+ rte_free(output);
+ rte_free(digest);
+ return 0;
+
+failed:
+ if (env.debug)
+ printf("FAIL %s tcId=%" JSON_INTEGER_FORMAT " result=%s status=%d ret=%d\n",
+ name, vector->tc_id, vector->result, status, ret);
+ stats->failed++;
+ rte_free(output);
+ rte_free(digest);
+ return 0;
+}
+
+static int
+process_aead(json_t *root, enum rte_crypto_aead_algorithm algorithm,
+ const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+
+ json_array_foreach(groups, group_index, group) {
+ json_t *tests = json_object_get(group, "tests");
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_array(tests))
+ return -EINVAL;
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ int ret = parse_vector(test, &vector);
+
+ if (ret != 0) {
+ if (env.debug)
+ printf("SKIP %s group=%zu test=%zu: malformed vector\n",
+ name, group_index, test_index);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ ret = validate_aead_vector(&vector, algorithm, name, stats);
+ if (ret == -ETIMEDOUT)
+ return -ETIMEDOUT;
+ free_vector(&vector);
+ }
+ }
+
+ return 0;
+}
+
+static int
+process_hmac(json_t *root, enum rte_crypto_auth_algorithm algorithm,
+ const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *tests = json_object_get(group, "tests");
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_array(tests))
+ return -EINVAL;
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *digest = NULL;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ int ret;
+
+ ret = parse_mac_vector(test, &vector);
+ if (ret != 0) {
+ if (env.debug)
+ printf("SKIP %s group=%zu test=%zu: malformed vector\n",
+ name, group_index, test_index);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, &vector, "acceptable result policy");
+ } else if (strcmp(vector.result, "valid") == 0) {
+ ret = run_hmac(&vector, algorithm, RTE_CRYPTO_AUTH_OP_GENERATE,
+ &digest, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ } else if (ret == -ETIMEDOUT) {
+ return -ETIMEDOUT;
+ } else if (ret == -EMSGSIZE) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector,
+ "message length exceeds mbuf");
+ } else if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS ||
+ (vector.tag_len != 0 &&
+ memcmp(digest, vector.tag, vector.tag_len) != 0)) {
+ debug_vector("FAIL", name, &vector,
+ "generated tag mismatch");
+ stats->failed++;
+ } else
+ stats->passed++;
+ } else if (strcmp(vector.result, "invalid") == 0) {
+ ret = run_hmac(&vector, algorithm, RTE_CRYPTO_AUTH_OP_VERIFY,
+ &digest, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ } else if (ret == -ETIMEDOUT) {
+ return -ETIMEDOUT;
+ } else if (ret == -EMSGSIZE) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector,
+ "message length exceeds mbuf");
+ } else if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_AUTH_FAILED) {
+ debug_vector("FAIL", name, &vector,
+ "authentication unexpectedly succeeded");
+ stats->failed++;
+ } else
+ stats->passed++;
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "unknown result");
+ }
+ rte_free(digest);
+ free_vector(&vector);
+ }
+ }
+ return 0;
+}
+
+static int
+process_gmac(json_t *root, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *tests = json_object_get(group, "tests");
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_array(tests))
+ return -EINVAL;
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *digest = NULL;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ int ret;
+
+ ret = parse_mac_with_iv_vector(test, &vector);
+ if (ret != 0) {
+ if (env.debug)
+ printf("SKIP AES-GMAC group=%zu test=%zu: "
+ "malformed vector\n",
+ group_index, test_index);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (vector.iv_len > MAX_IV_LEN) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", "AES-GMAC", &vector, "IV too large");
+ rte_free(digest);
+ free_vector(&vector);
+ continue;
+ }
+ if (strcmp(vector.result, "valid") == 0)
+ ret = run_gmac(&vector, RTE_CRYPTO_AUTH_OP_GENERATE,
+ &digest, &status);
+ else if (strcmp(vector.result, "invalid") == 0)
+ ret = run_gmac(&vector, RTE_CRYPTO_AUTH_OP_VERIFY,
+ &digest, &status);
+ else {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", "AES-GMAC", &vector,
+ "acceptable result policy");
+ ret = 0;
+ }
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", "AES-GMAC", &vector, "PMD capability");
+ } else if (ret == -ETIMEDOUT) {
+ return -ETIMEDOUT;
+ } else if (ret == -EMSGSIZE) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", "AES-GMAC", &vector,
+ "message length exceeds mbuf");
+ } else if ((strcmp(vector.result, "valid") == 0 &&
+ (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS ||
+ (vector.tag_len != 0 &&
+ memcmp(digest, vector.tag, vector.tag_len) != 0))) ||
+ (strcmp(vector.result, "invalid") == 0 &&
+ (ret != 0 || status != RTE_CRYPTO_OP_STATUS_AUTH_FAILED))) {
+ debug_vector("FAIL", "AES-GMAC", &vector,
+ "unexpected operation result");
+ stats->failed++;
+ } else if (strcmp(vector.result, "valid") == 0 ||
+ strcmp(vector.result, "invalid") == 0)
+ stats->passed++;
+ rte_free(digest);
+ free_vector(&vector);
+ }
+ }
+ return 0;
+}
+
+struct dsa_group {
+ uint8_t *p;
+ uint8_t *q;
+ uint8_t *g;
+ uint8_t *y;
+ uint32_t p_len;
+ uint32_t q_len;
+ uint32_t g_len;
+ uint32_t y_len;
+ enum rte_crypto_auth_algorithm hash;
+ uint32_t digest_len;
+};
+
+static int
+map_sha(const char *name, enum rte_crypto_auth_algorithm *algo, uint32_t *digest_len)
+{
+ if (strcmp(name, "SHA-1") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA1;
+ *digest_len = 20;
+ } else if (strcmp(name, "SHA-224") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA224;
+ *digest_len = 28;
+ } else if (strcmp(name, "SHA-256") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA256;
+ *digest_len = 32;
+ } else if (strcmp(name, "SHA-384") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA384;
+ *digest_len = 48;
+ } else if (strcmp(name, "SHA-512") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA512;
+ *digest_len = 64;
+ } else {
+ return -ENOTSUP;
+ }
+ return 0;
+}
+
+/* Compute a plain (keyless) message digest through the cryptodev auth path. */
+static int
+compute_hash(const uint8_t *msg, uint32_t msg_len, enum rte_crypto_auth_algorithm algo,
+ uint8_t *digest, uint32_t digest_len)
+{
+ struct rte_crypto_sym_xform xform = { 0 };
+ struct rte_cryptodev_sym_session *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_mbuf *mbuf = NULL;
+ const struct rte_cryptodev_symmetric_capability *capability;
+ struct rte_cryptodev_sym_capability_idx index = {
+ .type = RTE_CRYPTO_SYM_XFORM_AUTH,
+ .algo.auth = algo,
+ };
+ uint8_t *op_digest = NULL;
+ int ret = -ENOMEM;
+
+ capability = rte_cryptodev_sym_capability_get(env.hash_dev_id, &index);
+ if (capability == NULL || rte_cryptodev_sym_capability_check_auth(capability,
+ 0, digest_len, 0) != 0)
+ return -ENOTSUP;
+
+ xform.type = RTE_CRYPTO_SYM_XFORM_AUTH;
+ xform.auth.algo = algo;
+ xform.auth.op = RTE_CRYPTO_AUTH_OP_GENERATE;
+ xform.auth.digest_length = digest_len;
+ session = rte_cryptodev_sym_session_create(env.hash_dev_id, &xform, env.session_pool);
+ if (session == NULL)
+ goto out;
+
+ op = rte_crypto_op_alloc(env.op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC);
+ if (op == NULL)
+ goto out;
+ mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
+ if (mbuf == NULL)
+ goto out;
+ if (msg_len > rte_pktmbuf_tailroom(mbuf)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ if (msg_len != 0)
+ memcpy(rte_pktmbuf_append(mbuf, msg_len), msg, msg_len);
+
+ op_digest = rte_malloc(NULL, digest_len, RTE_CACHE_LINE_SIZE);
+ if (op_digest == NULL)
+ goto out;
+ op->sym->m_src = mbuf;
+ op->sym->auth.data.offset = 0;
+ op->sym->auth.data.length = msg_len;
+ op->sym->auth.digest.data = op_digest;
+ op->sym->auth.digest.phys_addr = rte_malloc_virt2iova(op_digest);
+ rte_crypto_op_attach_sym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.hash_dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.hash_dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+ if (completed->status != RTE_CRYPTO_OP_STATUS_SUCCESS) {
+ ret = -EIO;
+ goto out;
+ }
+ memcpy(digest, op_digest, digest_len);
+ ret = 0;
+
+out:
+ rte_free(op_digest);
+ rte_pktmbuf_free(mbuf);
+ rte_crypto_op_free(op);
+ rte_cryptodev_sym_session_free(env.hash_dev_id, session);
+ return ret;
+}
+
+static int
+run_dsa_verify(const struct dsa_group *group, const uint8_t *msg, uint32_t msg_len,
+ const uint8_t *r, uint32_t r_len, const uint8_t *s, uint32_t s_len,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_asym_xform xform = { 0 };
+ void *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_cryptodev_info dev_info;
+ struct rte_cryptodev_asym_capability_idx index = {
+ .type = RTE_CRYPTO_ASYM_XFORM_DSA,
+ };
+ const struct rte_cryptodev_asymmetric_xform_capability *capability;
+ uint8_t digest[64];
+ int ret;
+
+ if (env.asym_session_pool == NULL || env.asym_op_pool == NULL)
+ return -ENOTSUP;
+ rte_cryptodev_info_get(env.dev_id, &dev_info);
+ if ((dev_info.feature_flags & RTE_CRYPTODEV_FF_ASYMMETRIC_CRYPTO) == 0)
+ return -ENOTSUP;
+ capability = rte_cryptodev_asym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_asym_xform_capability_check_optype(capability,
+ RTE_CRYPTO_ASYM_OP_VERIFY) == 0)
+ return -ENOTSUP;
+
+ ret = compute_hash(msg, msg_len, group->hash, digest, group->digest_len);
+ if (ret != 0)
+ return ret;
+
+ xform.xform_type = RTE_CRYPTO_ASYM_XFORM_DSA;
+ xform.next = NULL;
+ xform.dsa.p.data = group->p;
+ xform.dsa.p.length = group->p_len;
+ xform.dsa.q.data = group->q;
+ xform.dsa.q.length = group->q_len;
+ xform.dsa.g.data = group->g;
+ xform.dsa.g.length = group->g_len;
+
+ /* Coarse capability check cannot confirm every parameter; treat a
+ * session-create failure as unsupported rather than a hard failure.
+ */
+ if (rte_cryptodev_asym_session_create(env.dev_id, &xform, env.asym_session_pool,
+ &session) < 0 || session == NULL) {
+ ret = -ENOTSUP;
+ goto out;
+ }
+ ret = -ENOMEM;
+ op = rte_crypto_op_alloc(env.asym_op_pool, RTE_CRYPTO_OP_TYPE_ASYMMETRIC);
+ if (op == NULL)
+ goto out;
+ op->asym->dsa.op_type = RTE_CRYPTO_ASYM_OP_VERIFY;
+ /* DSA verify operates on the message digest. */
+ op->asym->dsa.message.data = digest;
+ op->asym->dsa.message.length = group->digest_len;
+ op->asym->dsa.r.data = (uint8_t *)(uintptr_t)r;
+ op->asym->dsa.r.length = r_len;
+ op->asym->dsa.s.data = (uint8_t *)(uintptr_t)s;
+ op->asym->dsa.s.length = s_len;
+ op->asym->dsa.y.data = group->y;
+ op->asym->dsa.y.length = group->y_len;
+ rte_crypto_op_attach_asym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+ *status = completed->status;
+ ret = 0;
+
+out:
+ rte_crypto_op_free(op);
+ if (session != NULL)
+ rte_cryptodev_asym_session_free(env.dev_id, session);
+ return ret;
+}
+
+static int
+process_dsa_p1363(json_t *root, const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *pub = json_object_get(group, "publicKey");
+ json_t *tests = json_object_get(group, "tests");
+ struct dsa_group dsa = { 0 };
+ const char *sha;
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_object(pub) || !json_is_array(tests))
+ return -EINVAL;
+ sha = json_string_value(json_object_get(group, "sha"));
+ if (sha == NULL || map_sha(sha, &dsa.hash, &dsa.digest_len) != 0) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (decode_hex(json_object_get(pub, "p"), &dsa.p, &dsa.p_len) != 0 ||
+ decode_hex(json_object_get(pub, "q"), &dsa.q, &dsa.q_len) != 0 ||
+ decode_hex(json_object_get(pub, "g"), &dsa.g, &dsa.g_len) != 0 ||
+ decode_hex(json_object_get(pub, "y"), &dsa.y, &dsa.y_len) != 0) {
+ rte_free(dsa.p);
+ rte_free(dsa.q);
+ rte_free(dsa.g);
+ rte_free(dsa.y);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *sig = NULL;
+ uint32_t sig_len = 0;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ json_t *tc_id;
+ int ret;
+
+ memset(&vector, 0, sizeof(vector));
+ tc_id = json_object_get(test, "tcId");
+ vector.result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector.result == NULL) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ vector.tc_id = json_integer_value(tc_id);
+ if (decode_hex(json_object_get(test, "msg"), &vector.msg,
+ &vector.msg_len) != 0 ||
+ decode_hex(json_object_get(test, "sig"),
+ &sig, &sig_len) != 0) {
+ rte_free(vector.msg);
+ rte_free(sig);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, &vector, "acceptable result policy");
+ rte_free(vector.msg);
+ rte_free(sig);
+ continue;
+ }
+ if ((sig_len & 1) != 0 || sig_len == 0) {
+ debug_vector("SKIP", name, &vector, "malformed P1363 signature");
+ stats->skipped_unsupported++;
+ rte_free(vector.msg);
+ rte_free(sig);
+ continue;
+ }
+ ret = run_dsa_verify(&dsa, vector.msg, vector.msg_len, sig, sig_len / 2,
+ sig + sig_len / 2, sig_len / 2, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ } else if (ret == -ETIMEDOUT) {
+ return -ETIMEDOUT;
+ } else if (ret == -EMSGSIZE) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "message length exceeds mbuf");
+ } else if ((strcmp(vector.result, "valid") == 0 &&
+ (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS)) ||
+ (strcmp(vector.result, "invalid") == 0 &&
+ (ret != 0 || status == RTE_CRYPTO_OP_STATUS_SUCCESS))) {
+ debug_vector("FAIL", name, &vector,
+ "unexpected verification result");
+ stats->failed++;
+ } else if (strcmp(vector.result, "valid") == 0 ||
+ strcmp(vector.result, "invalid") == 0) {
+ stats->passed++;
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "unknown result");
+ }
+ rte_free(vector.msg);
+ rte_free(sig);
+ }
+ rte_free(dsa.p);
+ rte_free(dsa.q);
+ rte_free(dsa.g);
+ rte_free(dsa.y);
+ }
+ return 0;
+}
+
+static int
+map_curve(const char *name, enum rte_crypto_curve_id *curve, uint32_t *bytesize)
+{
+ if (strcmp(name, "secp224r1") == 0) {
+ *curve = RTE_CRYPTO_EC_GROUP_SECP224R1;
+ *bytesize = 28;
+ } else if (strcmp(name, "secp256r1") == 0) {
+ *curve = RTE_CRYPTO_EC_GROUP_SECP256R1;
+ *bytesize = 32;
+ } else if (strcmp(name, "secp384r1") == 0) {
+ *curve = RTE_CRYPTO_EC_GROUP_SECP384R1;
+ *bytesize = 48;
+ } else if (strcmp(name, "secp521r1") == 0) {
+ *curve = RTE_CRYPTO_EC_GROUP_SECP521R1;
+ *bytesize = 66;
+ } else {
+ return -ENOTSUP;
+ }
+ return 0;
+}
+
+/* Left-align an unsigned big-endian value into a fixed-width field. */
+static bool
+value_equals_padded(const uint8_t *a, uint32_t a_len, const uint8_t *b, uint32_t b_len)
+{
+ while (a_len > 0 && a[0] == 0) {
+ a++;
+ a_len--;
+ }
+ while (b_len > 0 && b[0] == 0) {
+ b++;
+ b_len--;
+ }
+ return a_len == b_len && memcmp(a, b, a_len) == 0;
+}
+
+/* Left-pad a big-endian value into a fixed-width field: strip leading zeros,
+ * reject values wider than the field, zero-pad the remaining prefix.
+ */
+static int
+normalize_operand(const uint8_t *value, uint32_t value_len, uint8_t *out, uint32_t bytesize)
+{
+ while (value_len > 0 && value[0] == 0) {
+ value++;
+ value_len--;
+ }
+ if (value_len > bytesize)
+ return -EINVAL;
+ memset(out, 0, bytesize);
+ memcpy(out + bytesize - value_len, value, value_len);
+ return 0;
+}
+
+static int
+run_ecdh_ecpoint(enum rte_crypto_curve_id curve, uint32_t bytesize,
+ const uint8_t *priv, uint32_t priv_len, const uint8_t *pub_x,
+ const uint8_t *pub_y, uint8_t **shared_x, uint32_t *shared_len,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_asym_xform xform = { 0 };
+ void *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_cryptodev_info dev_info;
+ struct rte_cryptodev_asym_capability_idx index = {
+ .type = RTE_CRYPTO_ASYM_XFORM_ECDH,
+ };
+ const struct rte_cryptodev_asymmetric_xform_capability *capability;
+ uint8_t *out_x = NULL;
+ uint8_t *out_y = NULL;
+ uint8_t priv_buf[MAX_EC_BYTESIZE];
+ int ret;
+
+ *shared_x = NULL;
+ if (env.asym_session_pool == NULL || env.asym_op_pool == NULL)
+ return -ENOTSUP;
+ rte_cryptodev_info_get(env.dev_id, &dev_info);
+ if ((dev_info.feature_flags & RTE_CRYPTODEV_FF_ASYMMETRIC_CRYPTO) == 0)
+ return -ENOTSUP;
+ capability = rte_cryptodev_asym_capability_get(env.dev_id, &index);
+ if (capability == NULL)
+ return -ENOTSUP;
+ if (rte_cryptodev_asym_xform_capability_check_optype(capability,
+ (enum rte_crypto_asym_op_type)
+ RTE_CRYPTO_ASYM_KE_SHARED_SECRET_COMPUTE) == 0)
+ return -ENOTSUP;
+
+ if (bytesize > sizeof(priv_buf))
+ return -ENOTSUP;
+ if (normalize_operand(priv, priv_len, priv_buf, bytesize) != 0)
+ return -EINVAL;
+
+ xform.xform_type = RTE_CRYPTO_ASYM_XFORM_ECDH;
+ xform.next = NULL;
+ xform.ec.curve_id = curve;
+ xform.ec.pkey.data = priv_buf;
+ xform.ec.pkey.length = bytesize;
+ xform.ec.q.x.data = (uint8_t *)(uintptr_t)pub_x;
+ xform.ec.q.x.length = bytesize;
+ xform.ec.q.y.data = (uint8_t *)(uintptr_t)pub_y;
+ xform.ec.q.y.length = bytesize;
+
+ ret = -ENOMEM;
+ out_x = rte_zmalloc(NULL, bytesize, 0);
+ out_y = rte_zmalloc(NULL, bytesize, 0);
+ if (out_x == NULL || out_y == NULL)
+ goto out;
+ /* Coarse capability check cannot confirm curve support; treat a
+ * session-create failure as unsupported rather than a hard failure.
+ */
+ if (rte_cryptodev_asym_session_create(env.dev_id, &xform, env.asym_session_pool,
+ &session) < 0 || session == NULL) {
+ ret = -ENOTSUP;
+ goto out;
+ }
+ op = rte_crypto_op_alloc(env.asym_op_pool, RTE_CRYPTO_OP_TYPE_ASYMMETRIC);
+ if (op == NULL)
+ goto out;
+ op->asym->ecdh.ke_type = RTE_CRYPTO_ASYM_KE_SHARED_SECRET_COMPUTE;
+ op->asym->ecdh.priv_key.data = priv_buf;
+ op->asym->ecdh.priv_key.length = bytesize;
+ op->asym->ecdh.pub_key.x.data = (uint8_t *)(uintptr_t)pub_x;
+ op->asym->ecdh.pub_key.x.length = bytesize;
+ op->asym->ecdh.pub_key.y.data = (uint8_t *)(uintptr_t)pub_y;
+ op->asym->ecdh.pub_key.y.length = bytesize;
+ op->asym->ecdh.shared_secret.x.data = out_x;
+ op->asym->ecdh.shared_secret.x.length = bytesize;
+ op->asym->ecdh.shared_secret.y.data = out_y;
+ op->asym->ecdh.shared_secret.y.length = bytesize;
+ rte_crypto_op_attach_asym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+ *status = completed->status;
+ if (completed->status == RTE_CRYPTO_OP_STATUS_SUCCESS) {
+ *shared_x = out_x;
+ *shared_len = completed->asym->ecdh.shared_secret.x.length;
+ out_x = NULL;
+ }
+ ret = 0;
+
+out:
+ rte_free(out_x);
+ rte_free(out_y);
+ rte_crypto_op_free(op);
+ if (session != NULL)
+ rte_cryptodev_asym_session_free(env.dev_id, session);
+ return ret;
+}
+
+static int
+process_ecdh_ecpoint(json_t *root, const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *tests = json_object_get(group, "tests");
+ enum rte_crypto_curve_id curve;
+ uint32_t bytesize;
+ const char *curve_name;
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_array(tests))
+ return -EINVAL;
+ curve_name = json_string_value(json_object_get(group, "curve"));
+ if (curve_name == NULL || map_curve(curve_name, &curve, &bytesize) != 0) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *pub = NULL;
+ uint8_t *priv = NULL;
+ uint8_t *shared = NULL;
+ uint8_t *result_x = NULL;
+ uint32_t pub_len = 0, priv_len = 0, shared_len = 0, result_len = 0;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ bool matched;
+ json_t *tc_id;
+ int ret;
+
+ memset(&vector, 0, sizeof(vector));
+ tc_id = json_object_get(test, "tcId");
+ vector.result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector.result == NULL) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ vector.tc_id = json_integer_value(tc_id);
+ if (decode_hex(json_object_get(test, "public"), &pub, &pub_len) != 0 ||
+ decode_hex(json_object_get(test, "private"), &priv,
+ &priv_len) != 0 ||
+ decode_hex(json_object_get(test, "shared"), &shared,
+ &shared_len) != 0) {
+ rte_free(pub);
+ rte_free(priv);
+ rte_free(shared);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, &vector, "acceptable result policy");
+ goto next;
+ }
+ /* Only uncompressed points (0x04 || x || y) map to the DPDK EC point. */
+ if (pub_len != 1 + 2 * bytesize || pub[0] != 0x04) {
+ debug_vector("SKIP", name, &vector,
+ "non-uncompressed public point");
+ stats->skipped_unsupported++;
+ goto next;
+ }
+ /* Invalid-curve rejection cases carry no expected shared secret;
+ * on-curve validation is a separate ECDH PUB_KEY_VERIFY op, not
+ * part of the raw shared-secret compute primitive.
+ */
+ if (shared_len == 0) {
+ debug_vector("SKIP", name, &vector,
+ "no shared secret (point validation out of scope)");
+ stats->skipped_unsupported++;
+ goto next;
+ }
+ ret = run_ecdh_ecpoint(curve, bytesize, priv, priv_len, pub + 1,
+ pub + 1 + bytesize, &result_x, &result_len, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ goto next;
+ }
+ if (ret == -ETIMEDOUT)
+ return -ETIMEDOUT;
+ if (ret == -EINVAL) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "operand wider than curve");
+ goto next;
+ }
+ matched = ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS &&
+ value_equals_padded(result_x, result_len, shared, shared_len);
+ if (strcmp(vector.result, "valid") == 0) {
+ if (ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS && matched) {
+ stats->passed++;
+ } else if (ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS) {
+ debug_vector("FAIL", name, &vector, "wrong shared secret");
+ stats->failed++;
+ } else {
+ /* PMD returned an error for a valid vector. */
+ debug_vector("FAIL", name, &vector,
+ "PMD could not compute");
+ stats->failed++;
+ }
+ } else if (strcmp(vector.result, "invalid") == 0) {
+ if (matched) {
+ debug_vector("FAIL", name, &vector,
+ "produced forbidden secret");
+ stats->failed++;
+ } else {
+ stats->passed++;
+ }
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "unknown result");
+ }
+next:
+ rte_free(result_x);
+ rte_free(pub);
+ rte_free(priv);
+ rte_free(shared);
+ }
+ }
+ return 0;
+}
+
+struct ecdsa_group {
+ uint8_t *wx;
+ uint8_t *wy;
+ uint32_t wx_len;
+ uint32_t wy_len;
+ enum rte_crypto_curve_id curve;
+ uint32_t bytesize;
+ enum rte_crypto_auth_algorithm hash;
+ uint32_t digest_len;
+};
+
+static int
+run_ecdsa_verify(const struct ecdsa_group *group, const uint8_t *msg, uint32_t msg_len,
+ const uint8_t *r, uint32_t r_len, const uint8_t *s, uint32_t s_len,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_asym_xform xform = { 0 };
+ void *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_cryptodev_info dev_info;
+ struct rte_cryptodev_asym_capability_idx index = {
+ .type = RTE_CRYPTO_ASYM_XFORM_ECDSA,
+ };
+ const struct rte_cryptodev_asymmetric_xform_capability *capability;
+ uint8_t digest[64];
+ uint8_t wx_buf[MAX_EC_BYTESIZE];
+ uint8_t wy_buf[MAX_EC_BYTESIZE];
+ uint32_t e_len;
+ int ret;
+
+ if (env.asym_session_pool == NULL || env.asym_op_pool == NULL)
+ return -ENOTSUP;
+ rte_cryptodev_info_get(env.dev_id, &dev_info);
+ if ((dev_info.feature_flags & RTE_CRYPTODEV_FF_ASYMMETRIC_CRYPTO) == 0)
+ return -ENOTSUP;
+ capability = rte_cryptodev_asym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_asym_xform_capability_check_optype(capability,
+ RTE_CRYPTO_ASYM_OP_VERIFY) == 0)
+ return -ENOTSUP;
+
+ ret = compute_hash(msg, msg_len, group->hash, digest, group->digest_len);
+ if (ret != 0)
+ return ret;
+ /* ECDSA uses e = leftmost Ln bits of the hash (byte-aligned truncation). */
+ e_len = group->digest_len > group->bytesize ? group->bytesize : group->digest_len;
+
+ if (group->bytesize > sizeof(wx_buf))
+ return -ENOTSUP;
+ if (normalize_operand(group->wx, group->wx_len, wx_buf, group->bytesize) != 0 ||
+ normalize_operand(group->wy, group->wy_len, wy_buf, group->bytesize) != 0)
+ return -EINVAL;
+
+ xform.xform_type = RTE_CRYPTO_ASYM_XFORM_ECDSA;
+ xform.next = NULL;
+ xform.ec.curve_id = group->curve;
+ xform.ec.q.x.data = wx_buf;
+ xform.ec.q.x.length = group->bytesize;
+ xform.ec.q.y.data = wy_buf;
+ xform.ec.q.y.length = group->bytesize;
+
+ /* Coarse capability check cannot confirm curve support; treat a
+ * session-create failure as unsupported rather than a hard failure.
+ */
+ if (rte_cryptodev_asym_session_create(env.dev_id, &xform, env.asym_session_pool,
+ &session) < 0 || session == NULL) {
+ ret = -ENOTSUP;
+ goto out;
+ }
+ ret = -ENOMEM;
+ op = rte_crypto_op_alloc(env.asym_op_pool, RTE_CRYPTO_OP_TYPE_ASYMMETRIC);
+ if (op == NULL)
+ goto out;
+ op->asym->ecdsa.op_type = RTE_CRYPTO_ASYM_OP_VERIFY;
+ op->asym->ecdsa.message.data = digest;
+ op->asym->ecdsa.message.length = e_len;
+ op->asym->ecdsa.r.data = (uint8_t *)(uintptr_t)r;
+ op->asym->ecdsa.r.length = r_len;
+ op->asym->ecdsa.s.data = (uint8_t *)(uintptr_t)s;
+ op->asym->ecdsa.s.length = s_len;
+ rte_crypto_op_attach_asym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+ *status = completed->status;
+ ret = 0;
+
+out:
+ rte_crypto_op_free(op);
+ if (session != NULL)
+ rte_cryptodev_asym_session_free(env.dev_id, session);
+ return ret;
+}
+
+static int
+process_ecdsa_p1363(json_t *root, const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *pub = json_object_get(group, "publicKey");
+ json_t *tests = json_object_get(group, "tests");
+ struct ecdsa_group ec = { 0 };
+ const char *sha;
+ const char *curve_name;
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_object(pub) || !json_is_array(tests))
+ return -EINVAL;
+ sha = json_string_value(json_object_get(group, "sha"));
+ curve_name = json_string_value(json_object_get(pub, "curve"));
+ if (sha == NULL || curve_name == NULL ||
+ map_sha(sha, &ec.hash, &ec.digest_len) != 0 ||
+ map_curve(curve_name, &ec.curve, &ec.bytesize) != 0) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (decode_hex(json_object_get(pub, "wx"), &ec.wx, &ec.wx_len) != 0 ||
+ decode_hex(json_object_get(pub, "wy"), &ec.wy, &ec.wy_len) != 0) {
+ rte_free(ec.wx);
+ rte_free(ec.wy);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *sig = NULL;
+ uint32_t sig_len = 0;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ json_t *tc_id;
+ int ret;
+
+ memset(&vector, 0, sizeof(vector));
+ tc_id = json_object_get(test, "tcId");
+ vector.result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector.result == NULL) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ vector.tc_id = json_integer_value(tc_id);
+ if (decode_hex(json_object_get(test, "msg"), &vector.msg,
+ &vector.msg_len) != 0 ||
+ decode_hex(json_object_get(test, "sig"),
+ &sig, &sig_len) != 0) {
+ rte_free(vector.msg);
+ rte_free(sig);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, &vector, "acceptable result policy");
+ rte_free(vector.msg);
+ rte_free(sig);
+ continue;
+ }
+ if (sig_len != 2 * ec.bytesize) {
+ debug_vector("SKIP", name, &vector,
+ "non-canonical P1363 signature size");
+ stats->skipped_unsupported++;
+ rte_free(vector.msg);
+ rte_free(sig);
+ continue;
+ }
+ ret = run_ecdsa_verify(&ec, vector.msg, vector.msg_len, sig, sig_len / 2,
+ sig + sig_len / 2, sig_len / 2, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ } else if (ret == -ETIMEDOUT) {
+ return -ETIMEDOUT;
+ } else if (ret == -EMSGSIZE) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "message length exceeds mbuf");
+ } else if (ret == -EINVAL) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "operand wider than curve");
+ } else if (strcmp(vector.result, "valid") == 0) {
+ if (ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS)
+ stats->passed++;
+ else {
+ debug_vector("FAIL", name, &vector,
+ "valid signature rejected");
+ stats->failed++;
+ }
+ } else if (strcmp(vector.result, "invalid") == 0) {
+ if (ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS) {
+ debug_vector("FAIL", name, &vector,
+ "invalid signature accepted");
+ stats->failed++;
+ } else {
+ stats->passed++;
+ }
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "unknown result");
+ }
+ rte_free(vector.msg);
+ rte_free(sig);
+ }
+ rte_free(ec.wx);
+ rte_free(ec.wy);
+ }
+ return 0;
+}
+
+static int
+process_file(const char *path, struct app_stats *stats)
+{
+ json_error_t error;
+ json_t *root;
+ const char *algorithm;
+ const char *schema;
+ int ret;
+
+ root = json_load_file(path, 0, &error);
+ if (root == NULL) {
+ if (env.debug)
+ printf("SKIP %s: JSON error at line %d: %s\n", path, error.line,
+ error.text);
+ return 0;
+ }
+ algorithm = json_string_value(json_object_get(root, "algorithm"));
+ schema = json_string_value(json_object_get(root, "schema"));
+ if (algorithm == NULL || schema == NULL) {
+ debug_file("SKIP", path, "missing algorithm or schema");
+ stats->skipped_unsupported++;
+ json_decref(root);
+ return 0;
+ }
+ if (strcmp(algorithm, "AES-GCM") == 0 &&
+ strcmp(schema, "aead_test_schema_v1.json") == 0) {
+ printf("Processing AES-GCM vectors: %s\n", path);
+ ret = process_aead(root, RTE_CRYPTO_AEAD_AES_GCM, "AES-GCM", stats);
+ } else if (strcmp(algorithm, "AES-CCM") == 0 &&
+ strcmp(schema, "aead_test_schema_v1.json") == 0) {
+ printf("Processing AES-CCM vectors: %s\n", path);
+ ret = process_aead(root, RTE_CRYPTO_AEAD_AES_CCM, "AES-CCM", stats);
+ } else if (strcmp(algorithm, "CHACHA20-POLY1305") == 0 &&
+ strcmp(schema, "aead_test_schema_v1.json") == 0) {
+ printf("Processing ChaCha20-Poly1305 vectors: %s\n", path);
+ ret = process_aead(root, RTE_CRYPTO_AEAD_CHACHA20_POLY1305,
+ "ChaCha20-Poly1305", stats);
+ } else if (strcmp(algorithm, "SM4-GCM") == 0 &&
+ strcmp(schema, "aead_test_schema_v1.json") == 0) {
+ printf("Processing SM4-GCM vectors: %s\n", path);
+ ret = process_aead(root, RTE_CRYPTO_AEAD_SM4_GCM, "SM4-GCM", stats);
+ } else if (strcmp(algorithm, "HMACSHA256") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA256 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA256_HMAC, "HMAC-SHA256", stats);
+ } else if (strcmp(algorithm, "HMACSHA1") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA1 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA1_HMAC, "HMAC-SHA1", stats);
+ } else if (strcmp(algorithm, "HMACSHA224") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA224 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA224_HMAC, "HMAC-SHA224", stats);
+ } else if (strcmp(algorithm, "HMACSHA384") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA384 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA384_HMAC, "HMAC-SHA384", stats);
+ } else if (strcmp(algorithm, "HMACSHA512") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA512 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA512_HMAC, "HMAC-SHA512", stats);
+ } else if (strcmp(algorithm, "HMACSHA3-224") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA3-224 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA3_224_HMAC, "HMAC-SHA3-224", stats);
+ } else if (strcmp(algorithm, "HMACSHA3-256") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA3-256 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA3_256_HMAC, "HMAC-SHA3-256", stats);
+ } else if (strcmp(algorithm, "HMACSHA3-384") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA3-384 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA3_384_HMAC, "HMAC-SHA3-384", stats);
+ } else if (strcmp(algorithm, "HMACSHA3-512") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA3-512 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA3_512_HMAC, "HMAC-SHA3-512", stats);
+ } else if (strcmp(algorithm, "HMACSM3") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SM3 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SM3_HMAC, "HMAC-SM3", stats);
+ } else if (strcmp(algorithm, "AES-CMAC") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing AES-CMAC vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_AES_CMAC, "AES-CMAC", stats);
+ } else if (strcmp(algorithm, "AES-GMAC") == 0 &&
+ strcmp(schema, "mac_with_iv_test_schema_v1.json") == 0) {
+ printf("Processing AES-GMAC vectors: %s\n", path);
+ ret = process_gmac(root, stats);
+ } else if (strcmp(algorithm, "DSA") == 0 &&
+ strcmp(schema, "dsa_p1363_verify_schema_v1.json") == 0) {
+ printf("Processing DSA (P1363) vectors: %s\n", path);
+ ret = process_dsa_p1363(root, "DSA", stats);
+ } else if (strcmp(algorithm, "ECDH") == 0 &&
+ strcmp(schema, "ecdh_ecpoint_test_schema_v1.json") == 0) {
+ printf("Processing ECDH (ecpoint) vectors: %s\n", path);
+ ret = process_ecdh_ecpoint(root, "ECDH", stats);
+ } else if (strcmp(algorithm, "ECDSA") == 0 &&
+ strcmp(schema, "ecdsa_p1363_verify_schema_v1.json") == 0) {
+ printf("Processing ECDSA (P1363) vectors: %s\n", path);
+ ret = process_ecdsa_p1363(root, "ECDSA", stats);
+ } else if (strncmp(algorithm, "AES-", strlen("AES-")) == 0 ||
+ strncmp(algorithm, "SEED-", strlen("SEED-")) == 0 ||
+ strcmp(algorithm, "SM4-CCM") == 0 ||
+ strncmp(algorithm, "HMAC", strlen("HMAC")) == 0 ||
+ strncmp(algorithm, "RSA", strlen("RSA")) == 0 ||
+ strcmp(algorithm, "DSA") == 0 ||
+ strcmp(algorithm, "ECDH") == 0 ||
+ strcmp(algorithm, "ECDSA") == 0) {
+ debug_file("SKIP", path, algorithm);
+ stats->skipped_unsupported++;
+ ret = 0;
+ } else {
+ debug_file("SKIP", path, "unsupported vector file");
+ stats->skipped_unsupported++;
+ ret = 0;
+ }
+ json_decref(root);
+ return ret;
+}
+
+static int
+process_path(const char *path, struct app_stats *stats)
+{
+ struct stat path_stat;
+ DIR *directory;
+ struct dirent *entry;
+ int ret = 0;
+
+ if (stat(path, &path_stat) != 0)
+ return -errno;
+ if (!S_ISDIR(path_stat.st_mode))
+ return process_file(path, stats);
+
+ directory = opendir(path);
+ if (directory == NULL)
+ return -errno;
+ while ((entry = readdir(directory)) != NULL) {
+ char file_path[PATH_MAX];
+ size_t name_length = strlen(entry->d_name);
+
+ if (name_length < 6 || strcmp(entry->d_name + name_length - 5, ".json") != 0)
+ continue;
+ if (snprintf(file_path, sizeof(file_path), "%s/%s", path, entry->d_name) >=
+ (int)sizeof(file_path)) {
+ ret = -ENAMETOOLONG;
+ break;
+ }
+ ret = process_file(file_path, stats);
+ if (ret != 0)
+ break;
+ }
+ closedir(directory);
+ return ret;
+}
+
+int
+main(int argc, char **argv)
+{
+ struct app_stats stats = { 0 };
+ int eal_args;
+ int ret;
+
+ eal_args = rte_eal_init(argc, argv);
+ if (eal_args < 0)
+ rte_exit(EXIT_FAILURE, "Cannot initialize EAL\n");
+ argc -= eal_args;
+ argv += eal_args;
+
+ ret = parse_args(argc, argv);
+ if (ret != 0) {
+ usage(argv[0]);
+ ret = EXIT_FAILURE;
+ goto out_eal;
+ }
+ ret = app_init();
+ if (ret != 0) {
+ printf("Cannot initialize cryptodev %u: %s\n", env.dev_id, rte_strerror(-ret));
+ ret = EXIT_FAILURE;
+ goto out_eal;
+ }
+
+ ret = process_path(env.vectors_path, &stats);
+ if (ret != 0)
+ printf("Vector processing failed: %s\n", rte_strerror(-ret));
+ printf("Summary: passed=%" PRIu64 " failed=%" PRIu64
+ " skipped_capability=%" PRIu64 " skipped_acceptable=%" PRIu64
+ " skipped_unsupported=%" PRIu64 "\n", stats.passed, stats.failed,
+ stats.skipped_capability, stats.skipped_acceptable, stats.skipped_unsupported);
+ app_uninit();
+ ret = ret == 0 && stats.failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+
+out_eal:
+ rte_eal_cleanup();
+ return ret;
+}
diff --git a/examples/wycheproof_validation/meson.build b/examples/wycheproof_validation/meson.build
new file mode 100644
index 0000000000..7f18b4cef3
--- /dev/null
+++ b/examples/wycheproof_validation/meson.build
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026
+
+# meson file, for building this example as part of a main DPDK build.
+#
+# To build this example as a standalone application with an already-installed
+# DPDK instance, use 'make'
+
+deps += ['cryptodev']
+
+if not dpdk_conf.has('RTE_HAS_JANSSON')
+ build = false
+ message('Skipping wycheproof_validation: Jansson is required')
+else
+ ext_deps += jansson_dep
+ sources = files('main.c')
+endif
--
2.34.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH v7] examples: add Wycheproof validation app
2026-09-23 10:50 ` [PATCH v6] " Kai Ji
@ 2026-09-23 15:04 ` Kai Ji
2026-09-24 14:35 ` [EXTERNAL] " Akhil Goyal
0 siblings, 1 reply; 17+ messages in thread
From: Kai Ji @ 2026-09-23 15:04 UTC (permalink / raw)
To: dev; +Cc: Kai Ji, Thomas Monjalon
Add a Wycheproof JSON vector validation example for cryptodev PMDs.
Support these algorithms when advertised by the selected PMD:
- AEAD: AES-GCM, AES-CCM, SM4-GCM, ChaCha20-Poly1305
- MAC: AES-CMAC, AES-GMAC, HMAC SHA-1/SHA-2/SHA-3/SM3
- Asymmetric: DSA (P1363 verify), ECDSA (P1363 verify),
ECDH (ecpoint shared-secret compute)
Validate valid vectors against generated ciphertexts, tags, plaintexts,
digests, shared secrets, or signature verification, and require the
expected rejection for invalid vectors. Digest inputs for DSA and ECDSA
use the symmetric auth path, selecting a separate symmetric-capable
device when the target device is asymmetric-only.
Skip parameter combinations outside PMD capability ranges and identify
recognized vector families without a compatible DPDK transform. A
--debug option lists every failed or skipped vector.
Add Meson and standalone build integration, with usage documentation.
Signed-off-by: Kai Ji <kai.ji@intel.com>
---
MAINTAINERS | 5 +
doc/guides/rel_notes/release_26_11.rst | 6 +
doc/guides/sample_app_ug/index.rst | 1 +
.../sample_app_ug/wycheproof_validation.rst | 50 +
examples/meson.build | 1 +
examples/wycheproof_validation/Makefile | 40 +
examples/wycheproof_validation/main.c | 2090 +++++++++++++++++
examples/wycheproof_validation/meson.build | 17 +
8 files changed, 2210 insertions(+)
create mode 100644 doc/guides/sample_app_ug/wycheproof_validation.rst
create mode 100644 examples/wycheproof_validation/Makefile
create mode 100644 examples/wycheproof_validation/main.c
create mode 100644 examples/wycheproof_validation/meson.build
diff --git a/MAINTAINERS b/MAINTAINERS
index 186cc82b39..580e2c2591 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2099,3 +2099,8 @@ F: examples/vmdq/
F: doc/guides/sample_app_ug/vmdq_forwarding.rst
F: examples/vmdq_dcb/
F: doc/guides/sample_app_ug/vmdq_dcb_forwarding.rst
+
+Wycheproof validation example
+M: Kai Ji <kai.ji@intel.com>
+F: examples/wycheproof_validation/
+F: doc/guides/sample_app_ug/wycheproof_validation.rst
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index dec96ccbc7..cd2711ddad 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -133,6 +133,12 @@ New Features
with per-descriptor mbuf free (``rte_pktmbuf_free_seg``) and prefetch hints.
* Changed the set of per-queue xstats counters.
+* **Added Wycheproof validation example application.**
+
+ Added a new example application that validates a DPDK cryptodev PMD against
+ the Google Wycheproof JSON test vectors, covering AEAD, MAC, DSA, ECDH and
+ ECDSA algorithm families.
+
Removed Items
-------------
diff --git a/doc/guides/sample_app_ug/index.rst b/doc/guides/sample_app_ug/index.rst
index 28c4c9a5b2..2458b06ccd 100644
--- a/doc/guides/sample_app_ug/index.rst
+++ b/doc/guides/sample_app_ug/index.rst
@@ -51,6 +51,7 @@ Sample Applications User Guides
ptpclient
ptp_tap_relay_sw
fips_validation
+ wycheproof_validation
ipsec_secgw
bbdev_app
ntb
diff --git a/doc/guides/sample_app_ug/wycheproof_validation.rst b/doc/guides/sample_app_ug/wycheproof_validation.rst
new file mode 100644
index 0000000000..17c8bd2b55
--- /dev/null
+++ b/doc/guides/sample_app_ug/wycheproof_validation.rst
@@ -0,0 +1,50 @@
+.. SPDX-License-Identifier: BSD-3-Clause
+ Copyright(c) 2026 Intel Corporation.
+
+Wycheproof Validation Example
+=============================
+
+Overview
+--------
+
+This example validates a DPDK cryptodev implementation against the Google
+Wycheproof JSON test vectors.
+
+The application reads one JSON file or a directory of JSON files at runtime and
+checks the supported algorithm families against the selected PMD. It can be used
+with a PMD that advertises AEAD, MAC, DSA, ECDH, or ECDSA support.
+
+Build
+-----
+
+Build the example from the DPDK tree with Meson:
+
+.. code-block:: console
+
+ meson setup build -Dexamples=wycheproof_validation -Denable_drivers=crypto/openssl
+ meson compile -C build
+
+Standalone Makefile builds are also supported from the example directory.
+
+Run
+---
+
+Run the example with an OpenSSL-backed cryptodev and a vector file:
+
+.. code-block:: console
+
+ ./build/examples/dpdk-wycheproof_validation --vdev crypto_openssl -- \
+ --vectors ../wycheproof/testvectors_v1/aes_gcm_test.json \
+ --cryptodev crypto_openssl --debug
+
+The program accepts a single JSON file or a directory of JSON files. Use
+``--debug`` to print each failed or skipped vector with its reason.
+
+Asymmetric coverage (DSA, ECDH, ECDSA) requires a PMD that advertises the
+corresponding asymmetric xform capabilities. The ``crypto_openssl`` PMD used
+above does not advertise the ECDSA or ECDH xforms, so those vectors are skipped;
+select a PMD that advertises them to exercise the asymmetric families.
+
+The exit code is nonzero when a parsed and supported vector fails validation;
+unsupported vectors, capability-skipped cases, and acceptable results are reported
+in the summary without forcing a failure.
diff --git a/examples/meson.build b/examples/meson.build
index 544ec5eadd..156efe447f 100644
--- a/examples/meson.build
+++ b/examples/meson.build
@@ -58,6 +58,7 @@ all_examples = [
'vm_power_manager/guest_cli',
'vmdq',
'vmdq_dcb',
+ 'wycheproof_validation',
]
# on install, skip copying all meson.build files
diff --git a/examples/wycheproof_validation/Makefile b/examples/wycheproof_validation/Makefile
new file mode 100644
index 0000000000..8cc0293364
--- /dev/null
+++ b/examples/wycheproof_validation/Makefile
@@ -0,0 +1,40 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026
+
+APP = wycheproof_validation
+SRCS-y := main.c
+
+PKGCONF ?= pkg-config
+
+ifneq ($(shell $(PKGCONF) --exists libdpdk && echo 0),0)
+$(error "no installation of DPDK found")
+endif
+ifneq ($(shell $(PKGCONF) --exists jansson && echo 0),0)
+$(error "Jansson is required")
+endif
+
+all: shared
+.PHONY: shared static
+shared: build/$(APP)-shared
+ ln -sf $(APP)-shared build/$(APP)
+static: build/$(APP)-static
+ ln -sf $(APP)-static build/$(APP)
+
+PC_FILE := $(shell $(PKGCONF) --path libdpdk 2>/dev/null)
+CFLAGS += -O3 $(shell $(PKGCONF) --cflags libdpdk jansson)
+LDFLAGS_SHARED = $(shell $(PKGCONF) --libs libdpdk jansson)
+LDFLAGS_STATIC = $(shell $(PKGCONF) --static --libs libdpdk jansson)
+
+build/$(APP)-shared: $(SRCS-y) Makefile $(PC_FILE) | build
+ $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_SHARED)
+
+build/$(APP)-static: $(SRCS-y) Makefile $(PC_FILE) | build
+ $(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_STATIC)
+
+build:
+ @mkdir -p $@
+
+.PHONY: clean
+clean:
+ rm -f build/$(APP) build/$(APP)-static build/$(APP)-shared
+ test -d build && rmdir -p build || true
diff --git a/examples/wycheproof_validation/main.c b/examples/wycheproof_validation/main.c
new file mode 100644
index 0000000000..d22d26e949
--- /dev/null
+++ b/examples/wycheproof_validation/main.c
@@ -0,0 +1,2090 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026
+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <jansson.h>
+
+#include <rte_cryptodev.h>
+#include <rte_cycles.h>
+#include <rte_eal.h>
+#include <rte_errno.h>
+#include <rte_mbuf.h>
+#include <rte_mempool.h>
+#include <rte_malloc.h>
+
+#define DEFAULT_MBUF_DATA_ROOM 16384
+#define NUM_QP_DESCRIPTORS 128
+#define MAX_IV_LEN 512
+#define MAX_EC_BYTESIZE 66
+/* CCM AAD is prefixed with an 18-byte reserved area (B0 block + length). */
+#define AES_CCM_AAD_OFFSET 18
+#define IV_OFFSET (sizeof(struct rte_crypto_op) + sizeof(struct rte_crypto_sym_op))
+
+struct wycheproof_data {
+ uint8_t *key;
+ uint8_t *iv;
+ uint8_t *aad;
+ uint8_t *msg;
+ uint8_t *ct;
+ uint8_t *tag;
+ uint32_t key_len;
+ uint32_t iv_len;
+ uint32_t aad_len;
+ uint32_t msg_len;
+ uint32_t ct_len;
+ uint32_t tag_len;
+ json_int_t tc_id;
+ const char *result;
+};
+
+struct app_env {
+ uint8_t dev_id;
+ uint8_t hash_dev_id;
+ bool hash_dev_own;
+ uint32_t mbuf_data_room;
+ const char *vectors_path;
+ bool debug;
+ struct rte_mempool *mbuf_pool;
+ struct rte_mempool *session_pool;
+ struct rte_mempool *op_pool;
+ struct rte_mempool *asym_session_pool;
+ struct rte_mempool *asym_op_pool;
+};
+
+struct app_stats {
+ uint64_t passed;
+ uint64_t failed;
+ uint64_t skipped_capability;
+ uint64_t skipped_acceptable;
+ uint64_t skipped_unsupported;
+};
+
+static struct app_env env = {
+ .mbuf_data_room = DEFAULT_MBUF_DATA_ROOM,
+};
+
+static void
+usage(const char *program)
+{
+ printf("%s [EAL options] -- "
+ "--vectors PATH [--cryptodev NAME | --cryptodev-id ID] [--debug]\n",
+ program);
+}
+
+static void
+debug_vector(const char *type, const char *algorithm,
+ const struct wycheproof_data *vector, const char *reason)
+{
+ if (env.debug)
+ printf("%s %s tcId=%" JSON_INTEGER_FORMAT " result=%s: %s\n", type,
+ algorithm, vector->tc_id, vector->result, reason);
+}
+
+static void
+debug_file(const char *type, const char *path, const char *reason)
+{
+ if (env.debug)
+ printf("%s %s: %s\n", type, path, reason);
+}
+
+static int
+parse_uint32(const char *text, uint32_t *value)
+{
+ char *end;
+ unsigned long parsed;
+
+ errno = 0;
+ parsed = strtoul(text, &end, 10);
+ if (errno != 0 || *text == '\0' || *end != '\0' || parsed > UINT32_MAX)
+ return -EINVAL;
+
+ *value = parsed;
+ return 0;
+}
+
+static int
+parse_args(int argc, char **argv)
+{
+ static const struct option options[] = {
+ { "vectors", required_argument, NULL, 'v' },
+ { "cryptodev", required_argument, NULL, 'c' },
+ { "cryptodev-id", required_argument, NULL, 'd' },
+ { "mbuf-dataroom", required_argument, NULL, 'm' },
+ { "debug", no_argument, NULL, 'D' },
+ { NULL, 0, NULL, 0 },
+ };
+ bool have_device = false;
+ int option;
+
+ if (rte_cryptodev_count() == 0)
+ return -ENODEV;
+ env.dev_id = 0;
+
+ while ((option = getopt_long(argc, argv, "v:c:d:m:D", options, NULL)) != -1) {
+ uint32_t value;
+ int dev_id;
+
+ switch (option) {
+ case 'v':
+ env.vectors_path = optarg;
+ break;
+ case 'c':
+ dev_id = rte_cryptodev_get_dev_id(optarg);
+ if (dev_id < 0)
+ return dev_id;
+ env.dev_id = dev_id;
+ have_device = true;
+ break;
+ case 'd':
+ if (parse_uint32(optarg, &value) != 0 || value > UINT8_MAX ||
+ !rte_cryptodev_is_valid_dev(value))
+ return -EINVAL;
+ env.dev_id = value;
+ have_device = true;
+ break;
+ case 'm':
+ if (parse_uint32(optarg, &env.mbuf_data_room) != 0 ||
+ env.mbuf_data_room < RTE_PKTMBUF_HEADROOM ||
+ env.mbuf_data_room > UINT16_MAX)
+ return -EINVAL;
+ break;
+ case 'D':
+ env.debug = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ if (env.vectors_path == NULL)
+ return -EINVAL;
+ if (!have_device)
+ printf("Using cryptodev %u (%s)\n", env.dev_id,
+ rte_cryptodev_name_get(env.dev_id));
+
+ return 0;
+}
+
+static int
+app_init(void)
+{
+ struct rte_cryptodev_config config = {
+ .socket_id = rte_socket_id(),
+ .nb_queue_pairs = 1,
+ .ff_disable = 0,
+ };
+ struct rte_cryptodev_qp_conf queue_pair = {
+ .nb_descriptors = NUM_QP_DESCRIPTORS,
+ .mp_session = NULL,
+ };
+ struct rte_cryptodev_info dev_info;
+ uint32_t session_size;
+ int ret;
+
+ rte_cryptodev_info_get(env.dev_id, &dev_info);
+ if (dev_info.max_nb_queue_pairs < config.nb_queue_pairs)
+ return -ENOTSUP;
+
+ ret = rte_cryptodev_configure(env.dev_id, &config);
+ if (ret < 0)
+ return ret;
+
+ env.mbuf_pool = rte_pktmbuf_pool_create("WYCHEPROOF_MBUF_POOL", 64, 0, 0,
+ env.mbuf_data_room, rte_socket_id());
+ if (env.mbuf_pool == NULL) {
+ ret = -rte_errno;
+ goto error;
+ }
+
+ session_size = rte_cryptodev_sym_get_private_session_size(env.dev_id);
+ {
+ /* The hashing device may differ from the target device, so size the
+ * session pool for the largest sym session across all devices.
+ */
+ uint8_t d;
+
+ for (d = 0; d < rte_cryptodev_count(); d++) {
+ uint32_t s = rte_cryptodev_sym_get_private_session_size(d);
+
+ if (s > session_size)
+ session_size = s;
+ }
+ }
+ env.session_pool = rte_cryptodev_sym_session_pool_create("WYCHEPROOF_SESSION_POOL",
+ 64, session_size, 0, 0, rte_socket_id());
+ if (env.session_pool == NULL) {
+ ret = -rte_errno;
+ goto error;
+ }
+
+ /* priv_size is measured from the end of the sym op; the IV lives there. */
+ env.op_pool = rte_crypto_op_pool_create("WYCHEPROOF_OP_POOL",
+ RTE_CRYPTO_OP_TYPE_SYMMETRIC, 64, 0, MAX_IV_LEN, rte_socket_id());
+ if (env.op_pool == NULL) {
+ ret = -rte_errno;
+ goto error;
+ }
+
+ /* Asymmetric pools are best-effort; DSA/ECDSA paths gate on the
+ * device asymmetric feature flag at runtime.
+ */
+ env.asym_session_pool = rte_cryptodev_asym_session_pool_create(
+ "WYCHEPROOF_ASYM_SESS", 16, 0, 0, rte_socket_id());
+ env.asym_op_pool = rte_crypto_op_pool_create("WYCHEPROOF_ASYM_OP",
+ RTE_CRYPTO_OP_TYPE_ASYMMETRIC, 64, 0, 0, rte_socket_id());
+
+ queue_pair.mp_session = env.session_pool;
+ ret = rte_cryptodev_queue_pair_setup(env.dev_id, 0, &queue_pair,
+ rte_socket_id());
+ if (ret < 0)
+ goto error;
+
+ ret = rte_cryptodev_start(env.dev_id);
+ if (ret < 0)
+ goto error;
+
+ /* Digest computation for DSA/ECDSA needs a symmetric-auth device. The
+ * target device may be asym-only (for example QAT), so pick a separate
+ * sym-capable device for hashing when needed.
+ */
+ env.hash_dev_id = env.dev_id;
+ env.hash_dev_own = false;
+ {
+ struct rte_cryptodev_sym_capability_idx hash_idx = {
+ .type = RTE_CRYPTO_SYM_XFORM_AUTH,
+ .algo.auth = RTE_CRYPTO_AUTH_SHA256,
+ };
+ uint8_t count = rte_cryptodev_count();
+ uint8_t d;
+
+ if (rte_cryptodev_sym_capability_get(env.dev_id, &hash_idx) == NULL) {
+ for (d = 0; d < count; d++) {
+ struct rte_cryptodev_qp_conf hash_qp = {
+ .nb_descriptors = NUM_QP_DESCRIPTORS,
+ .mp_session = NULL,
+ };
+
+ if (d == env.dev_id)
+ continue;
+ if (rte_cryptodev_sym_capability_get(d, &hash_idx) == NULL)
+ continue;
+ if (rte_cryptodev_configure(d, &config) < 0)
+ continue;
+ hash_qp.mp_session = env.session_pool;
+ if (rte_cryptodev_queue_pair_setup(d, 0, &hash_qp,
+ rte_socket_id()) < 0 ||
+ rte_cryptodev_start(d) < 0) {
+ rte_cryptodev_close(d);
+ continue;
+ }
+ env.hash_dev_id = d;
+ env.hash_dev_own = true;
+ break;
+ }
+ }
+ }
+
+ return 0;
+
+error:
+ rte_mempool_free(env.asym_op_pool);
+ rte_mempool_free(env.asym_session_pool);
+ rte_mempool_free(env.op_pool);
+ rte_mempool_free(env.session_pool);
+ rte_mempool_free(env.mbuf_pool);
+ env.asym_op_pool = NULL;
+ env.asym_session_pool = NULL;
+ env.op_pool = NULL;
+ env.session_pool = NULL;
+ env.mbuf_pool = NULL;
+ rte_cryptodev_close(env.dev_id);
+ return ret;
+}
+
+static void
+app_uninit(void)
+{
+ if (env.hash_dev_own) {
+ rte_cryptodev_stop(env.hash_dev_id);
+ rte_cryptodev_close(env.hash_dev_id);
+ }
+ rte_cryptodev_stop(env.dev_id);
+ rte_cryptodev_close(env.dev_id);
+ rte_mempool_free(env.asym_op_pool);
+ rte_mempool_free(env.asym_session_pool);
+ rte_mempool_free(env.op_pool);
+ rte_mempool_free(env.session_pool);
+ rte_mempool_free(env.mbuf_pool);
+}
+
+static void
+free_vector(struct wycheproof_data *vector)
+{
+ rte_free(vector->key);
+ rte_free(vector->iv);
+ rte_free(vector->aad);
+ rte_free(vector->msg);
+ rte_free(vector->ct);
+ rte_free(vector->tag);
+ memset(vector, 0, sizeof(*vector));
+}
+
+static int
+hex_value(char character)
+{
+ if (character >= '0' && character <= '9')
+ return character - '0';
+ if (character >= 'a' && character <= 'f')
+ return character - 'a' + 10;
+ if (character >= 'A' && character <= 'F')
+ return character - 'A' + 10;
+ return -1;
+}
+
+static int
+decode_hex(json_t *value, uint8_t **buffer, uint32_t *length)
+{
+ const char *hex;
+ size_t hex_length;
+ uint8_t *decoded;
+ size_t index;
+
+ if (!json_is_string(value))
+ return -EINVAL;
+
+ hex = json_string_value(value);
+ hex_length = strlen(hex);
+ if ((hex_length & 1) != 0 || hex_length / 2 > UINT32_MAX)
+ return -EINVAL;
+
+ *length = hex_length / 2;
+ if (*length == 0)
+ return 0;
+
+ decoded = rte_malloc(NULL, *length, 0);
+ if (decoded == NULL)
+ return -ENOMEM;
+
+ for (index = 0; index < *length; index++) {
+ int high = hex_value(hex[index * 2]);
+ int low = hex_value(hex[index * 2 + 1]);
+
+ if (high < 0 || low < 0) {
+ rte_free(decoded);
+ return -EINVAL;
+ }
+ decoded[index] = (high << 4) | low;
+ }
+
+ *buffer = decoded;
+ return 0;
+}
+
+static int
+parse_vector(json_t *test, struct wycheproof_data *vector)
+{
+ json_t *tc_id;
+ int ret;
+
+ memset(vector, 0, sizeof(*vector));
+ tc_id = json_object_get(test, "tcId");
+ vector->result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector->result == NULL)
+ return -EINVAL;
+ vector->tc_id = json_integer_value(tc_id);
+
+ ret = decode_hex(json_object_get(test, "key"), &vector->key, &vector->key_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "iv"), &vector->iv, &vector->iv_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "aad"), &vector->aad, &vector->aad_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "msg"), &vector->msg, &vector->msg_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "ct"), &vector->ct, &vector->ct_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "tag"), &vector->tag, &vector->tag_len);
+ if (ret != 0)
+ goto error;
+
+ return 0;
+
+error:
+ free_vector(vector);
+ return ret;
+}
+
+static int
+parse_mac_vector(json_t *test, struct wycheproof_data *vector)
+{
+ json_t *tc_id;
+ int ret;
+
+ memset(vector, 0, sizeof(*vector));
+ tc_id = json_object_get(test, "tcId");
+ vector->result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector->result == NULL)
+ return -EINVAL;
+ vector->tc_id = json_integer_value(tc_id);
+
+ ret = decode_hex(json_object_get(test, "key"), &vector->key, &vector->key_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "msg"), &vector->msg, &vector->msg_len);
+ if (ret != 0)
+ goto error;
+ ret = decode_hex(json_object_get(test, "tag"), &vector->tag, &vector->tag_len);
+ if (ret != 0)
+ goto error;
+
+ return 0;
+
+error:
+ free_vector(vector);
+ return ret;
+}
+
+static int
+parse_mac_with_iv_vector(json_t *test, struct wycheproof_data *vector)
+{
+ int ret;
+
+ ret = parse_mac_vector(test, vector);
+ if (ret != 0)
+ return ret;
+ ret = decode_hex(json_object_get(test, "iv"), &vector->iv, &vector->iv_len);
+ if (ret != 0)
+ free_vector(vector);
+ return ret;
+}
+
+/* Bounded wait for one completed op; NULL on timeout so a stuck PMD fails the
+ * vector instead of hanging the tool.
+ */
+static struct rte_crypto_op *
+dequeue_one(uint8_t dev_id)
+{
+ struct rte_crypto_op *completed = NULL;
+ uint64_t deadline = rte_get_timer_cycles() + rte_get_timer_hz();
+
+ while (rte_cryptodev_dequeue_burst(dev_id, 0, &completed, 1) == 0) {
+ if (rte_get_timer_cycles() > deadline)
+ return NULL;
+ rte_pause();
+ }
+ return completed;
+}
+
+static int
+check_aead_capability(const struct wycheproof_data *vector,
+ enum rte_crypto_aead_algorithm algorithm)
+{
+ struct rte_cryptodev_sym_capability_idx index = {
+ .type = RTE_CRYPTO_SYM_XFORM_AEAD,
+ .algo.aead = algorithm,
+ };
+ const struct rte_cryptodev_symmetric_capability *capability;
+
+ capability = rte_cryptodev_sym_capability_get(env.dev_id, &index);
+ if (capability == NULL)
+ return -ENOTSUP;
+
+ return rte_cryptodev_sym_capability_check_aead(capability, vector->key_len,
+ vector->tag_len, vector->aad_len, vector->iv_len);
+}
+
+static int
+run_aead(const struct wycheproof_data *vector, enum rte_crypto_aead_algorithm algorithm,
+ enum rte_crypto_aead_operation operation, uint8_t **output, uint8_t **digest,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_sym_xform xform = { 0 };
+ struct rte_cryptodev_sym_session *session = NULL;
+ struct rte_crypto_op *operation_op = NULL;
+ struct rte_mbuf *mbuf = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_crypto_sym_op *sym_op;
+ uint8_t *aad = NULL;
+ uint8_t *input;
+ uint32_t input_len;
+ int ret = -ENOMEM;
+
+ *output = NULL;
+ *digest = NULL;
+ xform.type = RTE_CRYPTO_SYM_XFORM_AEAD;
+ xform.aead.algo = algorithm;
+ xform.aead.op = operation;
+ xform.aead.key.data = vector->key;
+ xform.aead.key.length = vector->key_len;
+ xform.aead.iv.offset = IV_OFFSET;
+ xform.aead.iv.length = vector->iv_len;
+ xform.aead.aad_length = vector->aad_len;
+ xform.aead.digest_length = vector->tag_len;
+
+ session = rte_cryptodev_sym_session_create(env.dev_id, &xform, env.session_pool);
+ if (session == NULL)
+ goto out;
+
+ operation_op = rte_crypto_op_alloc(env.op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC);
+ if (operation_op == NULL)
+ goto out;
+
+ mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
+ if (mbuf == NULL)
+ goto out;
+ input = operation == RTE_CRYPTO_AEAD_OP_ENCRYPT ? vector->msg : vector->ct;
+ input_len = operation == RTE_CRYPTO_AEAD_OP_ENCRYPT ? vector->msg_len : vector->ct_len;
+ if (input_len > rte_pktmbuf_tailroom(mbuf)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ if (input_len != 0)
+ memcpy(rte_pktmbuf_append(mbuf, input_len), input, input_len);
+
+ *digest = rte_malloc(NULL, vector->tag_len, RTE_CACHE_LINE_SIZE);
+ if (*digest == NULL && vector->tag_len != 0)
+ goto out;
+ if (operation == RTE_CRYPTO_AEAD_OP_DECRYPT && vector->tag_len != 0)
+ memcpy(*digest, vector->tag, vector->tag_len);
+
+ sym_op = operation_op->sym;
+ sym_op->m_src = mbuf;
+ sym_op->aead.data.offset = 0;
+ sym_op->aead.data.length = input_len;
+ if (algorithm == RTE_CRYPTO_AEAD_AES_CCM) {
+ aad = rte_zmalloc(NULL,
+ RTE_ALIGN_CEIL(vector->aad_len + AES_CCM_AAD_OFFSET, 16), 0);
+ if (aad == NULL)
+ goto out;
+ if (vector->aad_len != 0)
+ memcpy(aad + AES_CCM_AAD_OFFSET, vector->aad, vector->aad_len);
+ sym_op->aead.aad.data = aad;
+ sym_op->aead.aad.phys_addr = rte_malloc_virt2iova(aad);
+ memcpy(rte_crypto_op_ctod_offset(operation_op, uint8_t *, IV_OFFSET) + 1,
+ vector->iv, vector->iv_len);
+ } else {
+ sym_op->aead.aad.data = vector->aad;
+ sym_op->aead.aad.phys_addr = rte_malloc_virt2iova(vector->aad);
+ memcpy(rte_crypto_op_ctod_offset(operation_op, uint8_t *, IV_OFFSET), vector->iv,
+ vector->iv_len);
+ }
+ sym_op->aead.digest.data = *digest;
+ sym_op->aead.digest.phys_addr = rte_malloc_virt2iova(*digest);
+ rte_crypto_op_attach_sym_session(operation_op, session);
+
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &operation_op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+
+ *status = completed->status;
+ if (*status == RTE_CRYPTO_OP_STATUS_SUCCESS && input_len != 0) {
+ *output = rte_malloc(NULL, input_len, 0);
+ if (*output == NULL)
+ goto out;
+ memcpy(*output, rte_pktmbuf_mtod(mbuf, uint8_t *), input_len);
+ }
+ ret = 0;
+
+out:
+ rte_free(aad);
+ rte_pktmbuf_free(mbuf);
+ rte_crypto_op_free(operation_op);
+ rte_cryptodev_sym_session_free(env.dev_id, session);
+ if (ret != 0) {
+ rte_free(*output);
+ rte_free(*digest);
+ *output = NULL;
+ *digest = NULL;
+ }
+ return ret;
+}
+
+static int
+run_hmac(const struct wycheproof_data *vector, enum rte_crypto_auth_algorithm algorithm,
+ enum rte_crypto_auth_operation operation, uint8_t **digest,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_sym_xform xform = { 0 };
+ struct rte_cryptodev_sym_session *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_mbuf *mbuf = NULL;
+ const struct rte_cryptodev_symmetric_capability *capability;
+ struct rte_cryptodev_sym_capability_idx index = {
+ .type = RTE_CRYPTO_SYM_XFORM_AUTH,
+ .algo.auth = algorithm,
+ };
+ int ret = -ENOMEM;
+
+ *digest = NULL;
+ capability = rte_cryptodev_sym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_sym_capability_check_auth(capability,
+ vector->key_len, vector->tag_len, 0) != 0)
+ return -ENOTSUP;
+
+ xform.type = RTE_CRYPTO_SYM_XFORM_AUTH;
+ xform.auth.algo = algorithm;
+ xform.auth.op = operation;
+ xform.auth.key.data = vector->key;
+ xform.auth.key.length = vector->key_len;
+ xform.auth.digest_length = vector->tag_len;
+ session = rte_cryptodev_sym_session_create(env.dev_id, &xform, env.session_pool);
+ if (session == NULL)
+ goto out;
+
+ op = rte_crypto_op_alloc(env.op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC);
+ if (op == NULL)
+ goto out;
+ mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
+ if (mbuf == NULL)
+ goto out;
+ if (vector->msg_len > rte_pktmbuf_tailroom(mbuf)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ if (vector->msg_len != 0)
+ memcpy(rte_pktmbuf_append(mbuf, vector->msg_len), vector->msg, vector->msg_len);
+
+ *digest = rte_malloc(NULL, vector->tag_len, RTE_CACHE_LINE_SIZE);
+ if (*digest == NULL && vector->tag_len != 0)
+ goto out;
+ if (operation == RTE_CRYPTO_AUTH_OP_VERIFY && vector->tag_len != 0)
+ memcpy(*digest, vector->tag, vector->tag_len);
+
+ op->sym->m_src = mbuf;
+ op->sym->auth.data.offset = 0;
+ op->sym->auth.data.length = vector->msg_len;
+ op->sym->auth.digest.data = *digest;
+ op->sym->auth.digest.phys_addr = rte_malloc_virt2iova(*digest);
+ rte_crypto_op_attach_sym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+ *status = completed->status;
+ ret = 0;
+
+out:
+ rte_pktmbuf_free(mbuf);
+ rte_crypto_op_free(op);
+ rte_cryptodev_sym_session_free(env.dev_id, session);
+ if (ret != 0) {
+ rte_free(*digest);
+ *digest = NULL;
+ }
+ return ret;
+}
+
+static int
+run_gmac(const struct wycheproof_data *vector, enum rte_crypto_auth_operation operation,
+ uint8_t **digest, enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_sym_xform xform = { 0 };
+ struct rte_cryptodev_sym_session *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_mbuf *mbuf = NULL;
+ const struct rte_cryptodev_symmetric_capability *capability;
+ struct rte_cryptodev_sym_capability_idx index = {
+ .type = RTE_CRYPTO_SYM_XFORM_AUTH,
+ .algo.auth = RTE_CRYPTO_AUTH_AES_GMAC,
+ };
+ int ret = -ENOMEM;
+
+ *digest = NULL;
+ capability = rte_cryptodev_sym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_sym_capability_check_auth(capability,
+ vector->key_len, vector->tag_len, vector->iv_len) != 0)
+ return -ENOTSUP;
+
+ xform.type = RTE_CRYPTO_SYM_XFORM_AUTH;
+ xform.auth.algo = RTE_CRYPTO_AUTH_AES_GMAC;
+ xform.auth.op = operation;
+ xform.auth.key.data = vector->key;
+ xform.auth.key.length = vector->key_len;
+ xform.auth.iv.offset = IV_OFFSET;
+ xform.auth.iv.length = vector->iv_len;
+ xform.auth.digest_length = vector->tag_len;
+ session = rte_cryptodev_sym_session_create(env.dev_id, &xform, env.session_pool);
+ if (session == NULL)
+ goto out;
+
+ op = rte_crypto_op_alloc(env.op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC);
+ if (op == NULL)
+ goto out;
+ mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
+ if (mbuf == NULL)
+ goto out;
+ if (vector->msg_len > rte_pktmbuf_tailroom(mbuf)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ if (vector->msg_len != 0)
+ memcpy(rte_pktmbuf_append(mbuf, vector->msg_len), vector->msg, vector->msg_len);
+
+ *digest = rte_malloc(NULL, vector->tag_len, RTE_CACHE_LINE_SIZE);
+ if (*digest == NULL && vector->tag_len != 0)
+ goto out;
+ if (operation == RTE_CRYPTO_AUTH_OP_VERIFY && vector->tag_len != 0)
+ memcpy(*digest, vector->tag, vector->tag_len);
+
+ op->sym->m_src = mbuf;
+ op->sym->auth.data.offset = 0;
+ op->sym->auth.data.length = vector->msg_len;
+ op->sym->auth.digest.data = *digest;
+ op->sym->auth.digest.phys_addr = rte_malloc_virt2iova(*digest);
+ memcpy(rte_crypto_op_ctod_offset(op, uint8_t *, IV_OFFSET), vector->iv, vector->iv_len);
+ rte_crypto_op_attach_sym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+ *status = completed->status;
+ ret = 0;
+
+out:
+ rte_pktmbuf_free(mbuf);
+ rte_crypto_op_free(op);
+ rte_cryptodev_sym_session_free(env.dev_id, session);
+ if (ret != 0) {
+ rte_free(*digest);
+ *digest = NULL;
+ }
+ return ret;
+}
+
+static int
+validate_aead_vector(const struct wycheproof_data *vector,
+ enum rte_crypto_aead_algorithm algorithm, const char *name,
+ struct app_stats *stats)
+{
+ uint8_t *output = NULL;
+ uint8_t *digest = NULL;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ int ret;
+
+ if (strcmp(vector->result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, vector, "acceptable result policy");
+ return 0;
+ }
+ if (strcmp(vector->result, "valid") != 0 && strcmp(vector->result, "invalid") != 0) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, vector, "unknown result");
+ return 0;
+ }
+ /* CCM writes the IV one byte into the reserved area (after the flags byte). */
+ if (vector->iv_len + (algorithm == RTE_CRYPTO_AEAD_AES_CCM ? 1u : 0u) > MAX_IV_LEN ||
+ vector->msg_len != vector->ct_len ||
+ vector->msg_len > env.mbuf_data_room - RTE_PKTMBUF_HEADROOM) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, vector, "unsupported message length");
+ return 0;
+ }
+ if (check_aead_capability(vector, algorithm) != 0) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, vector, "PMD capability");
+ return 0;
+ }
+
+ if (strcmp(vector->result, "valid") == 0) {
+ ret = run_aead(vector, algorithm, RTE_CRYPTO_AEAD_OP_ENCRYPT, &output, &digest,
+ &status);
+ if (ret == -ETIMEDOUT)
+ return -ETIMEDOUT;
+ if (ret == -EMSGSIZE)
+ goto unsupported;
+ if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS ||
+ (vector->ct_len != 0 &&
+ memcmp(output, vector->ct, vector->ct_len) != 0) ||
+ (vector->tag_len != 0 &&
+ memcmp(digest, vector->tag, vector->tag_len) != 0))
+ goto failed;
+ rte_free(output);
+ rte_free(digest);
+ output = NULL;
+ digest = NULL;
+ ret = run_aead(vector, algorithm, RTE_CRYPTO_AEAD_OP_DECRYPT, &output, &digest,
+ &status);
+ if (ret == -ETIMEDOUT)
+ return -ETIMEDOUT;
+ if (ret == -EMSGSIZE)
+ goto unsupported;
+ if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS ||
+ (vector->msg_len != 0 &&
+ memcmp(output, vector->msg, vector->msg_len) != 0))
+ goto failed;
+ } else {
+ ret = run_aead(vector, algorithm, RTE_CRYPTO_AEAD_OP_DECRYPT, &output, &digest,
+ &status);
+ if (ret == -ETIMEDOUT)
+ return -ETIMEDOUT;
+ if (ret == -EMSGSIZE)
+ goto unsupported;
+ if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_AUTH_FAILED)
+ goto failed;
+ }
+
+ stats->passed++;
+ rte_free(output);
+ rte_free(digest);
+ return 0;
+
+unsupported:
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, vector, "message length exceeds mbuf");
+ rte_free(output);
+ rte_free(digest);
+ return 0;
+
+failed:
+ if (env.debug)
+ printf("FAIL %s tcId=%" JSON_INTEGER_FORMAT " result=%s status=%d ret=%d\n",
+ name, vector->tc_id, vector->result, status, ret);
+ stats->failed++;
+ rte_free(output);
+ rte_free(digest);
+ return 0;
+}
+
+static int
+process_aead(json_t *root, enum rte_crypto_aead_algorithm algorithm,
+ const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+
+ json_array_foreach(groups, group_index, group) {
+ json_t *tests = json_object_get(group, "tests");
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_array(tests))
+ return -EINVAL;
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ int ret = parse_vector(test, &vector);
+
+ if (ret != 0) {
+ if (env.debug)
+ printf("SKIP %s group=%zu test=%zu: malformed vector\n",
+ name, group_index, test_index);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ ret = validate_aead_vector(&vector, algorithm, name, stats);
+ if (ret == -ETIMEDOUT)
+ return -ETIMEDOUT;
+ free_vector(&vector);
+ }
+ }
+
+ return 0;
+}
+
+static int
+process_hmac(json_t *root, enum rte_crypto_auth_algorithm algorithm,
+ const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *tests = json_object_get(group, "tests");
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_array(tests))
+ return -EINVAL;
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *digest = NULL;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ int ret;
+
+ ret = parse_mac_vector(test, &vector);
+ if (ret != 0) {
+ if (env.debug)
+ printf("SKIP %s group=%zu test=%zu: malformed vector\n",
+ name, group_index, test_index);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, &vector, "acceptable result policy");
+ } else if (strcmp(vector.result, "valid") == 0) {
+ ret = run_hmac(&vector, algorithm, RTE_CRYPTO_AUTH_OP_GENERATE,
+ &digest, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ } else if (ret == -ETIMEDOUT) {
+ return -ETIMEDOUT;
+ } else if (ret == -EMSGSIZE) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector,
+ "message length exceeds mbuf");
+ } else if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS ||
+ (vector.tag_len != 0 &&
+ memcmp(digest, vector.tag, vector.tag_len) != 0)) {
+ debug_vector("FAIL", name, &vector,
+ "generated tag mismatch");
+ stats->failed++;
+ } else
+ stats->passed++;
+ } else if (strcmp(vector.result, "invalid") == 0) {
+ ret = run_hmac(&vector, algorithm, RTE_CRYPTO_AUTH_OP_VERIFY,
+ &digest, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ } else if (ret == -ETIMEDOUT) {
+ return -ETIMEDOUT;
+ } else if (ret == -EMSGSIZE) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector,
+ "message length exceeds mbuf");
+ } else if (ret != 0 || status != RTE_CRYPTO_OP_STATUS_AUTH_FAILED) {
+ debug_vector("FAIL", name, &vector,
+ "authentication unexpectedly succeeded");
+ stats->failed++;
+ } else
+ stats->passed++;
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "unknown result");
+ }
+ rte_free(digest);
+ free_vector(&vector);
+ }
+ }
+ return 0;
+}
+
+static int
+process_gmac(json_t *root, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *tests = json_object_get(group, "tests");
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_array(tests))
+ return -EINVAL;
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *digest = NULL;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ int ret;
+
+ ret = parse_mac_with_iv_vector(test, &vector);
+ if (ret != 0) {
+ if (env.debug)
+ printf("SKIP AES-GMAC group=%zu test=%zu: "
+ "malformed vector\n",
+ group_index, test_index);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (vector.iv_len > MAX_IV_LEN) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", "AES-GMAC", &vector, "IV too large");
+ rte_free(digest);
+ free_vector(&vector);
+ continue;
+ }
+ if (strcmp(vector.result, "valid") == 0)
+ ret = run_gmac(&vector, RTE_CRYPTO_AUTH_OP_GENERATE,
+ &digest, &status);
+ else if (strcmp(vector.result, "invalid") == 0)
+ ret = run_gmac(&vector, RTE_CRYPTO_AUTH_OP_VERIFY,
+ &digest, &status);
+ else {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", "AES-GMAC", &vector,
+ "acceptable result policy");
+ ret = 0;
+ }
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", "AES-GMAC", &vector, "PMD capability");
+ } else if (ret == -ETIMEDOUT) {
+ return -ETIMEDOUT;
+ } else if (ret == -EMSGSIZE) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", "AES-GMAC", &vector,
+ "message length exceeds mbuf");
+ } else if ((strcmp(vector.result, "valid") == 0 &&
+ (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS ||
+ (vector.tag_len != 0 &&
+ memcmp(digest, vector.tag, vector.tag_len) != 0))) ||
+ (strcmp(vector.result, "invalid") == 0 &&
+ (ret != 0 || status != RTE_CRYPTO_OP_STATUS_AUTH_FAILED))) {
+ debug_vector("FAIL", "AES-GMAC", &vector,
+ "unexpected operation result");
+ stats->failed++;
+ } else if (strcmp(vector.result, "valid") == 0 ||
+ strcmp(vector.result, "invalid") == 0)
+ stats->passed++;
+ rte_free(digest);
+ free_vector(&vector);
+ }
+ }
+ return 0;
+}
+
+struct dsa_group {
+ uint8_t *p;
+ uint8_t *q;
+ uint8_t *g;
+ uint8_t *y;
+ uint32_t p_len;
+ uint32_t q_len;
+ uint32_t g_len;
+ uint32_t y_len;
+ enum rte_crypto_auth_algorithm hash;
+ uint32_t digest_len;
+};
+
+static int
+map_sha(const char *name, enum rte_crypto_auth_algorithm *algo, uint32_t *digest_len)
+{
+ if (strcmp(name, "SHA-1") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA1;
+ *digest_len = 20;
+ } else if (strcmp(name, "SHA-224") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA224;
+ *digest_len = 28;
+ } else if (strcmp(name, "SHA-256") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA256;
+ *digest_len = 32;
+ } else if (strcmp(name, "SHA-384") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA384;
+ *digest_len = 48;
+ } else if (strcmp(name, "SHA-512") == 0) {
+ *algo = RTE_CRYPTO_AUTH_SHA512;
+ *digest_len = 64;
+ } else {
+ return -ENOTSUP;
+ }
+ return 0;
+}
+
+/* Compute a plain (keyless) message digest through the cryptodev auth path. */
+static int
+compute_hash(const uint8_t *msg, uint32_t msg_len, enum rte_crypto_auth_algorithm algo,
+ uint8_t *digest, uint32_t digest_len)
+{
+ struct rte_crypto_sym_xform xform = { 0 };
+ struct rte_cryptodev_sym_session *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_mbuf *mbuf = NULL;
+ const struct rte_cryptodev_symmetric_capability *capability;
+ struct rte_cryptodev_sym_capability_idx index = {
+ .type = RTE_CRYPTO_SYM_XFORM_AUTH,
+ .algo.auth = algo,
+ };
+ uint8_t *op_digest = NULL;
+ int ret = -ENOMEM;
+
+ capability = rte_cryptodev_sym_capability_get(env.hash_dev_id, &index);
+ if (capability == NULL || rte_cryptodev_sym_capability_check_auth(capability,
+ 0, digest_len, 0) != 0)
+ return -ENOTSUP;
+
+ xform.type = RTE_CRYPTO_SYM_XFORM_AUTH;
+ xform.auth.algo = algo;
+ xform.auth.op = RTE_CRYPTO_AUTH_OP_GENERATE;
+ xform.auth.digest_length = digest_len;
+ session = rte_cryptodev_sym_session_create(env.hash_dev_id, &xform, env.session_pool);
+ if (session == NULL)
+ goto out;
+
+ op = rte_crypto_op_alloc(env.op_pool, RTE_CRYPTO_OP_TYPE_SYMMETRIC);
+ if (op == NULL)
+ goto out;
+ mbuf = rte_pktmbuf_alloc(env.mbuf_pool);
+ if (mbuf == NULL)
+ goto out;
+ if (msg_len > rte_pktmbuf_tailroom(mbuf)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ if (msg_len != 0)
+ memcpy(rte_pktmbuf_append(mbuf, msg_len), msg, msg_len);
+
+ op_digest = rte_malloc(NULL, digest_len, RTE_CACHE_LINE_SIZE);
+ if (op_digest == NULL)
+ goto out;
+ op->sym->m_src = mbuf;
+ op->sym->auth.data.offset = 0;
+ op->sym->auth.data.length = msg_len;
+ op->sym->auth.digest.data = op_digest;
+ op->sym->auth.digest.phys_addr = rte_malloc_virt2iova(op_digest);
+ rte_crypto_op_attach_sym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.hash_dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.hash_dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+ if (completed->status != RTE_CRYPTO_OP_STATUS_SUCCESS) {
+ ret = -EIO;
+ goto out;
+ }
+ memcpy(digest, op_digest, digest_len);
+ ret = 0;
+
+out:
+ rte_free(op_digest);
+ rte_pktmbuf_free(mbuf);
+ rte_crypto_op_free(op);
+ rte_cryptodev_sym_session_free(env.hash_dev_id, session);
+ return ret;
+}
+
+static int
+run_dsa_verify(const struct dsa_group *group, const uint8_t *msg, uint32_t msg_len,
+ const uint8_t *r, uint32_t r_len, const uint8_t *s, uint32_t s_len,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_asym_xform xform = { 0 };
+ void *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_cryptodev_info dev_info;
+ struct rte_cryptodev_asym_capability_idx index = {
+ .type = RTE_CRYPTO_ASYM_XFORM_DSA,
+ };
+ const struct rte_cryptodev_asymmetric_xform_capability *capability;
+ uint8_t digest[64];
+ int ret;
+
+ if (env.asym_session_pool == NULL || env.asym_op_pool == NULL)
+ return -ENOTSUP;
+ rte_cryptodev_info_get(env.dev_id, &dev_info);
+ if ((dev_info.feature_flags & RTE_CRYPTODEV_FF_ASYMMETRIC_CRYPTO) == 0)
+ return -ENOTSUP;
+ capability = rte_cryptodev_asym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_asym_xform_capability_check_optype(capability,
+ RTE_CRYPTO_ASYM_OP_VERIFY) == 0)
+ return -ENOTSUP;
+
+ ret = compute_hash(msg, msg_len, group->hash, digest, group->digest_len);
+ if (ret != 0)
+ return ret;
+
+ xform.xform_type = RTE_CRYPTO_ASYM_XFORM_DSA;
+ xform.next = NULL;
+ xform.dsa.p.data = group->p;
+ xform.dsa.p.length = group->p_len;
+ xform.dsa.q.data = group->q;
+ xform.dsa.q.length = group->q_len;
+ xform.dsa.g.data = group->g;
+ xform.dsa.g.length = group->g_len;
+
+ /* Coarse capability check cannot confirm every parameter; treat a
+ * session-create failure as unsupported rather than a hard failure.
+ */
+ if (rte_cryptodev_asym_session_create(env.dev_id, &xform, env.asym_session_pool,
+ &session) < 0 || session == NULL) {
+ ret = -ENOTSUP;
+ goto out;
+ }
+ ret = -ENOMEM;
+ op = rte_crypto_op_alloc(env.asym_op_pool, RTE_CRYPTO_OP_TYPE_ASYMMETRIC);
+ if (op == NULL)
+ goto out;
+ op->asym->dsa.op_type = RTE_CRYPTO_ASYM_OP_VERIFY;
+ /* DSA verify operates on the message digest. */
+ op->asym->dsa.message.data = digest;
+ op->asym->dsa.message.length = group->digest_len;
+ op->asym->dsa.r.data = (uint8_t *)(uintptr_t)r;
+ op->asym->dsa.r.length = r_len;
+ op->asym->dsa.s.data = (uint8_t *)(uintptr_t)s;
+ op->asym->dsa.s.length = s_len;
+ op->asym->dsa.y.data = group->y;
+ op->asym->dsa.y.length = group->y_len;
+ rte_crypto_op_attach_asym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+ *status = completed->status;
+ ret = 0;
+
+out:
+ rte_crypto_op_free(op);
+ if (session != NULL)
+ rte_cryptodev_asym_session_free(env.dev_id, session);
+ return ret;
+}
+
+static int
+process_dsa_p1363(json_t *root, const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *pub = json_object_get(group, "publicKey");
+ json_t *tests = json_object_get(group, "tests");
+ struct dsa_group dsa = { 0 };
+ const char *sha;
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_object(pub) || !json_is_array(tests))
+ return -EINVAL;
+ sha = json_string_value(json_object_get(group, "sha"));
+ if (sha == NULL || map_sha(sha, &dsa.hash, &dsa.digest_len) != 0) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (decode_hex(json_object_get(pub, "p"), &dsa.p, &dsa.p_len) != 0 ||
+ decode_hex(json_object_get(pub, "q"), &dsa.q, &dsa.q_len) != 0 ||
+ decode_hex(json_object_get(pub, "g"), &dsa.g, &dsa.g_len) != 0 ||
+ decode_hex(json_object_get(pub, "y"), &dsa.y, &dsa.y_len) != 0) {
+ rte_free(dsa.p);
+ rte_free(dsa.q);
+ rte_free(dsa.g);
+ rte_free(dsa.y);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *sig = NULL;
+ uint32_t sig_len = 0;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ json_t *tc_id;
+ int ret;
+
+ memset(&vector, 0, sizeof(vector));
+ tc_id = json_object_get(test, "tcId");
+ vector.result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector.result == NULL) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ vector.tc_id = json_integer_value(tc_id);
+ if (decode_hex(json_object_get(test, "msg"), &vector.msg,
+ &vector.msg_len) != 0 ||
+ decode_hex(json_object_get(test, "sig"),
+ &sig, &sig_len) != 0) {
+ rte_free(vector.msg);
+ rte_free(sig);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, &vector, "acceptable result policy");
+ rte_free(vector.msg);
+ rte_free(sig);
+ continue;
+ }
+ if ((sig_len & 1) != 0 || sig_len == 0) {
+ debug_vector("SKIP", name, &vector, "malformed P1363 signature");
+ stats->skipped_unsupported++;
+ rte_free(vector.msg);
+ rte_free(sig);
+ continue;
+ }
+ ret = run_dsa_verify(&dsa, vector.msg, vector.msg_len, sig, sig_len / 2,
+ sig + sig_len / 2, sig_len / 2, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ } else if (ret == -ETIMEDOUT) {
+ return -ETIMEDOUT;
+ } else if (ret == -EMSGSIZE) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "message length exceeds mbuf");
+ } else if ((strcmp(vector.result, "valid") == 0 &&
+ (ret != 0 || status != RTE_CRYPTO_OP_STATUS_SUCCESS)) ||
+ (strcmp(vector.result, "invalid") == 0 &&
+ (ret != 0 || status == RTE_CRYPTO_OP_STATUS_SUCCESS))) {
+ debug_vector("FAIL", name, &vector,
+ "unexpected verification result");
+ stats->failed++;
+ } else if (strcmp(vector.result, "valid") == 0 ||
+ strcmp(vector.result, "invalid") == 0) {
+ stats->passed++;
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "unknown result");
+ }
+ rte_free(vector.msg);
+ rte_free(sig);
+ }
+ rte_free(dsa.p);
+ rte_free(dsa.q);
+ rte_free(dsa.g);
+ rte_free(dsa.y);
+ }
+ return 0;
+}
+
+static int
+map_curve(const char *name, enum rte_crypto_curve_id *curve, uint32_t *bytesize)
+{
+ if (strcmp(name, "secp224r1") == 0) {
+ *curve = RTE_CRYPTO_EC_GROUP_SECP224R1;
+ *bytesize = 28;
+ } else if (strcmp(name, "secp256r1") == 0) {
+ *curve = RTE_CRYPTO_EC_GROUP_SECP256R1;
+ *bytesize = 32;
+ } else if (strcmp(name, "secp384r1") == 0) {
+ *curve = RTE_CRYPTO_EC_GROUP_SECP384R1;
+ *bytesize = 48;
+ } else if (strcmp(name, "secp521r1") == 0) {
+ *curve = RTE_CRYPTO_EC_GROUP_SECP521R1;
+ *bytesize = 66;
+ } else {
+ return -ENOTSUP;
+ }
+ return 0;
+}
+
+/* Left-align an unsigned big-endian value into a fixed-width field. */
+static bool
+value_equals_padded(const uint8_t *a, uint32_t a_len, const uint8_t *b, uint32_t b_len)
+{
+ while (a_len > 0 && a[0] == 0) {
+ a++;
+ a_len--;
+ }
+ while (b_len > 0 && b[0] == 0) {
+ b++;
+ b_len--;
+ }
+ return a_len == b_len && memcmp(a, b, a_len) == 0;
+}
+
+/* Left-pad a big-endian value into a fixed-width field: strip leading zeros,
+ * reject values wider than the field, zero-pad the remaining prefix.
+ */
+static int
+normalize_operand(const uint8_t *value, uint32_t value_len, uint8_t *out, uint32_t bytesize)
+{
+ while (value_len > 0 && value[0] == 0) {
+ value++;
+ value_len--;
+ }
+ if (value_len > bytesize)
+ return -EINVAL;
+ memset(out, 0, bytesize);
+ memcpy(out + bytesize - value_len, value, value_len);
+ return 0;
+}
+
+static int
+run_ecdh_ecpoint(enum rte_crypto_curve_id curve, uint32_t bytesize,
+ const uint8_t *priv, uint32_t priv_len, const uint8_t *pub_x,
+ const uint8_t *pub_y, uint8_t **shared_x, uint32_t *shared_len,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_asym_xform xform = { 0 };
+ void *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_cryptodev_info dev_info;
+ struct rte_cryptodev_asym_capability_idx index = {
+ .type = RTE_CRYPTO_ASYM_XFORM_ECDH,
+ };
+ const struct rte_cryptodev_asymmetric_xform_capability *capability;
+ uint8_t *out_x = NULL;
+ uint8_t *out_y = NULL;
+ uint8_t priv_buf[MAX_EC_BYTESIZE];
+ int ret;
+
+ *shared_x = NULL;
+ if (env.asym_session_pool == NULL || env.asym_op_pool == NULL)
+ return -ENOTSUP;
+ rte_cryptodev_info_get(env.dev_id, &dev_info);
+ if ((dev_info.feature_flags & RTE_CRYPTODEV_FF_ASYMMETRIC_CRYPTO) == 0)
+ return -ENOTSUP;
+ capability = rte_cryptodev_asym_capability_get(env.dev_id, &index);
+ if (capability == NULL)
+ return -ENOTSUP;
+ if (rte_cryptodev_asym_xform_capability_check_optype(capability,
+ (enum rte_crypto_asym_op_type)
+ RTE_CRYPTO_ASYM_KE_SHARED_SECRET_COMPUTE) == 0)
+ return -ENOTSUP;
+
+ if (bytesize > sizeof(priv_buf))
+ return -ENOTSUP;
+ if (normalize_operand(priv, priv_len, priv_buf, bytesize) != 0)
+ return -EINVAL;
+
+ xform.xform_type = RTE_CRYPTO_ASYM_XFORM_ECDH;
+ xform.next = NULL;
+ xform.ec.curve_id = curve;
+ xform.ec.pkey.data = priv_buf;
+ xform.ec.pkey.length = bytesize;
+ xform.ec.q.x.data = (uint8_t *)(uintptr_t)pub_x;
+ xform.ec.q.x.length = bytesize;
+ xform.ec.q.y.data = (uint8_t *)(uintptr_t)pub_y;
+ xform.ec.q.y.length = bytesize;
+
+ ret = -ENOMEM;
+ out_x = rte_zmalloc(NULL, bytesize, 0);
+ out_y = rte_zmalloc(NULL, bytesize, 0);
+ if (out_x == NULL || out_y == NULL)
+ goto out;
+ /* Coarse capability check cannot confirm curve support; treat a
+ * session-create failure as unsupported rather than a hard failure.
+ */
+ if (rte_cryptodev_asym_session_create(env.dev_id, &xform, env.asym_session_pool,
+ &session) < 0 || session == NULL) {
+ ret = -ENOTSUP;
+ goto out;
+ }
+ op = rte_crypto_op_alloc(env.asym_op_pool, RTE_CRYPTO_OP_TYPE_ASYMMETRIC);
+ if (op == NULL)
+ goto out;
+ op->asym->ecdh.ke_type = RTE_CRYPTO_ASYM_KE_SHARED_SECRET_COMPUTE;
+ op->asym->ecdh.priv_key.data = priv_buf;
+ op->asym->ecdh.priv_key.length = bytesize;
+ op->asym->ecdh.pub_key.x.data = (uint8_t *)(uintptr_t)pub_x;
+ op->asym->ecdh.pub_key.x.length = bytesize;
+ op->asym->ecdh.pub_key.y.data = (uint8_t *)(uintptr_t)pub_y;
+ op->asym->ecdh.pub_key.y.length = bytesize;
+ op->asym->ecdh.shared_secret.x.data = out_x;
+ op->asym->ecdh.shared_secret.x.length = bytesize;
+ op->asym->ecdh.shared_secret.y.data = out_y;
+ op->asym->ecdh.shared_secret.y.length = bytesize;
+ rte_crypto_op_attach_asym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+ *status = completed->status;
+ if (completed->status == RTE_CRYPTO_OP_STATUS_SUCCESS) {
+ *shared_x = out_x;
+ *shared_len = completed->asym->ecdh.shared_secret.x.length;
+ out_x = NULL;
+ }
+ ret = 0;
+
+out:
+ rte_free(out_x);
+ rte_free(out_y);
+ rte_crypto_op_free(op);
+ if (session != NULL)
+ rte_cryptodev_asym_session_free(env.dev_id, session);
+ return ret;
+}
+
+static int
+process_ecdh_ecpoint(json_t *root, const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *tests = json_object_get(group, "tests");
+ enum rte_crypto_curve_id curve;
+ uint32_t bytesize;
+ const char *curve_name;
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_array(tests))
+ return -EINVAL;
+ curve_name = json_string_value(json_object_get(group, "curve"));
+ if (curve_name == NULL || map_curve(curve_name, &curve, &bytesize) != 0) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *pub = NULL;
+ uint8_t *priv = NULL;
+ uint8_t *shared = NULL;
+ uint8_t *result_x = NULL;
+ uint32_t pub_len = 0, priv_len = 0, shared_len = 0, result_len = 0;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ bool matched;
+ json_t *tc_id;
+ int ret;
+
+ memset(&vector, 0, sizeof(vector));
+ tc_id = json_object_get(test, "tcId");
+ vector.result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector.result == NULL) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ vector.tc_id = json_integer_value(tc_id);
+ if (decode_hex(json_object_get(test, "public"), &pub, &pub_len) != 0 ||
+ decode_hex(json_object_get(test, "private"), &priv,
+ &priv_len) != 0 ||
+ decode_hex(json_object_get(test, "shared"), &shared,
+ &shared_len) != 0) {
+ rte_free(pub);
+ rte_free(priv);
+ rte_free(shared);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, &vector, "acceptable result policy");
+ goto next;
+ }
+ /* Only uncompressed points (0x04 || x || y) map to the DPDK EC point. */
+ if (pub_len != 1 + 2 * bytesize || pub[0] != 0x04) {
+ debug_vector("SKIP", name, &vector,
+ "non-uncompressed public point");
+ stats->skipped_unsupported++;
+ goto next;
+ }
+ /* Invalid-curve rejection cases carry no expected shared secret;
+ * on-curve validation is a separate ECDH PUB_KEY_VERIFY op, not
+ * part of the raw shared-secret compute primitive.
+ */
+ if (shared_len == 0) {
+ debug_vector("SKIP", name, &vector,
+ "no shared secret (point validation out of scope)");
+ stats->skipped_unsupported++;
+ goto next;
+ }
+ ret = run_ecdh_ecpoint(curve, bytesize, priv, priv_len, pub + 1,
+ pub + 1 + bytesize, &result_x, &result_len, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ goto next;
+ }
+ if (ret == -ETIMEDOUT)
+ return -ETIMEDOUT;
+ if (ret == -EINVAL) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "operand wider than curve");
+ goto next;
+ }
+ matched = ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS &&
+ value_equals_padded(result_x, result_len, shared, shared_len);
+ if (strcmp(vector.result, "valid") == 0) {
+ if (ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS && matched) {
+ stats->passed++;
+ } else if (ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS) {
+ debug_vector("FAIL", name, &vector, "wrong shared secret");
+ stats->failed++;
+ } else {
+ /* PMD returned an error for a valid vector. */
+ debug_vector("FAIL", name, &vector,
+ "PMD could not compute");
+ stats->failed++;
+ }
+ } else if (strcmp(vector.result, "invalid") == 0) {
+ if (matched) {
+ debug_vector("FAIL", name, &vector,
+ "produced forbidden secret");
+ stats->failed++;
+ } else {
+ stats->passed++;
+ }
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "unknown result");
+ }
+next:
+ rte_free(result_x);
+ rte_free(pub);
+ rte_free(priv);
+ rte_free(shared);
+ }
+ }
+ return 0;
+}
+
+struct ecdsa_group {
+ uint8_t *wx;
+ uint8_t *wy;
+ uint32_t wx_len;
+ uint32_t wy_len;
+ enum rte_crypto_curve_id curve;
+ uint32_t bytesize;
+ enum rte_crypto_auth_algorithm hash;
+ uint32_t digest_len;
+};
+
+static int
+run_ecdsa_verify(const struct ecdsa_group *group, const uint8_t *msg, uint32_t msg_len,
+ const uint8_t *r, uint32_t r_len, const uint8_t *s, uint32_t s_len,
+ enum rte_crypto_op_status *status)
+{
+ struct rte_crypto_asym_xform xform = { 0 };
+ void *session = NULL;
+ struct rte_crypto_op *op = NULL;
+ struct rte_crypto_op *completed = NULL;
+ struct rte_cryptodev_info dev_info;
+ struct rte_cryptodev_asym_capability_idx index = {
+ .type = RTE_CRYPTO_ASYM_XFORM_ECDSA,
+ };
+ const struct rte_cryptodev_asymmetric_xform_capability *capability;
+ uint8_t digest[64];
+ uint8_t wx_buf[MAX_EC_BYTESIZE];
+ uint8_t wy_buf[MAX_EC_BYTESIZE];
+ uint32_t e_len;
+ int ret;
+
+ if (env.asym_session_pool == NULL || env.asym_op_pool == NULL)
+ return -ENOTSUP;
+ rte_cryptodev_info_get(env.dev_id, &dev_info);
+ if ((dev_info.feature_flags & RTE_CRYPTODEV_FF_ASYMMETRIC_CRYPTO) == 0)
+ return -ENOTSUP;
+ capability = rte_cryptodev_asym_capability_get(env.dev_id, &index);
+ if (capability == NULL || rte_cryptodev_asym_xform_capability_check_optype(capability,
+ RTE_CRYPTO_ASYM_OP_VERIFY) == 0)
+ return -ENOTSUP;
+
+ ret = compute_hash(msg, msg_len, group->hash, digest, group->digest_len);
+ if (ret != 0)
+ return ret;
+ /* ECDSA uses e = leftmost Ln bits of the hash (byte-aligned truncation). */
+ e_len = group->digest_len > group->bytesize ? group->bytesize : group->digest_len;
+
+ if (group->bytesize > sizeof(wx_buf))
+ return -ENOTSUP;
+ if (normalize_operand(group->wx, group->wx_len, wx_buf, group->bytesize) != 0 ||
+ normalize_operand(group->wy, group->wy_len, wy_buf, group->bytesize) != 0)
+ return -EINVAL;
+
+ xform.xform_type = RTE_CRYPTO_ASYM_XFORM_ECDSA;
+ xform.next = NULL;
+ xform.ec.curve_id = group->curve;
+ xform.ec.q.x.data = wx_buf;
+ xform.ec.q.x.length = group->bytesize;
+ xform.ec.q.y.data = wy_buf;
+ xform.ec.q.y.length = group->bytesize;
+
+ /* Coarse capability check cannot confirm curve support; treat a
+ * session-create failure as unsupported rather than a hard failure.
+ */
+ if (rte_cryptodev_asym_session_create(env.dev_id, &xform, env.asym_session_pool,
+ &session) < 0 || session == NULL) {
+ ret = -ENOTSUP;
+ goto out;
+ }
+ ret = -ENOMEM;
+ op = rte_crypto_op_alloc(env.asym_op_pool, RTE_CRYPTO_OP_TYPE_ASYMMETRIC);
+ if (op == NULL)
+ goto out;
+ op->asym->ecdsa.op_type = RTE_CRYPTO_ASYM_OP_VERIFY;
+ op->asym->ecdsa.message.data = digest;
+ op->asym->ecdsa.message.length = e_len;
+ op->asym->ecdsa.r.data = (uint8_t *)(uintptr_t)r;
+ op->asym->ecdsa.r.length = r_len;
+ op->asym->ecdsa.s.data = (uint8_t *)(uintptr_t)s;
+ op->asym->ecdsa.s.length = s_len;
+ rte_crypto_op_attach_asym_session(op, session);
+ if (rte_cryptodev_enqueue_burst(env.dev_id, 0, &op, 1) != 1) {
+ ret = -EIO;
+ goto out;
+ }
+ completed = dequeue_one(env.dev_id);
+ if (completed == NULL)
+ /* Device wedged: op still owned by the PMD. Abort without freeing
+ * in-flight state; teardown stops the device.
+ */
+ return -ETIMEDOUT;
+ *status = completed->status;
+ ret = 0;
+
+out:
+ rte_crypto_op_free(op);
+ if (session != NULL)
+ rte_cryptodev_asym_session_free(env.dev_id, session);
+ return ret;
+}
+
+static int
+process_ecdsa_p1363(json_t *root, const char *name, struct app_stats *stats)
+{
+ json_t *groups = json_object_get(root, "testGroups");
+ json_t *group;
+ size_t group_index;
+
+ if (!json_is_array(groups))
+ return -EINVAL;
+ json_array_foreach(groups, group_index, group) {
+ json_t *pub = json_object_get(group, "publicKey");
+ json_t *tests = json_object_get(group, "tests");
+ struct ecdsa_group ec = { 0 };
+ const char *sha;
+ const char *curve_name;
+ json_t *test;
+ size_t test_index;
+
+ if (!json_is_object(pub) || !json_is_array(tests))
+ return -EINVAL;
+ sha = json_string_value(json_object_get(group, "sha"));
+ curve_name = json_string_value(json_object_get(pub, "curve"));
+ if (sha == NULL || curve_name == NULL ||
+ map_sha(sha, &ec.hash, &ec.digest_len) != 0 ||
+ map_curve(curve_name, &ec.curve, &ec.bytesize) != 0) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (decode_hex(json_object_get(pub, "wx"), &ec.wx, &ec.wx_len) != 0 ||
+ decode_hex(json_object_get(pub, "wy"), &ec.wy, &ec.wy_len) != 0) {
+ rte_free(ec.wx);
+ rte_free(ec.wy);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ json_array_foreach(tests, test_index, test) {
+ struct wycheproof_data vector;
+ uint8_t *sig = NULL;
+ uint32_t sig_len = 0;
+ enum rte_crypto_op_status status = RTE_CRYPTO_OP_STATUS_ERROR;
+ json_t *tc_id;
+ int ret;
+
+ memset(&vector, 0, sizeof(vector));
+ tc_id = json_object_get(test, "tcId");
+ vector.result = json_string_value(json_object_get(test, "result"));
+ if (!json_is_integer(tc_id) || vector.result == NULL) {
+ stats->skipped_unsupported++;
+ continue;
+ }
+ vector.tc_id = json_integer_value(tc_id);
+ if (decode_hex(json_object_get(test, "msg"), &vector.msg,
+ &vector.msg_len) != 0 ||
+ decode_hex(json_object_get(test, "sig"),
+ &sig, &sig_len) != 0) {
+ rte_free(vector.msg);
+ rte_free(sig);
+ stats->skipped_unsupported++;
+ continue;
+ }
+ if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", name, &vector, "acceptable result policy");
+ rte_free(vector.msg);
+ rte_free(sig);
+ continue;
+ }
+ if (sig_len != 2 * ec.bytesize) {
+ debug_vector("SKIP", name, &vector,
+ "non-canonical P1363 signature size");
+ stats->skipped_unsupported++;
+ rte_free(vector.msg);
+ rte_free(sig);
+ continue;
+ }
+ ret = run_ecdsa_verify(&ec, vector.msg, vector.msg_len, sig, sig_len / 2,
+ sig + sig_len / 2, sig_len / 2, &status);
+ if (ret == -ENOTSUP) {
+ stats->skipped_capability++;
+ debug_vector("SKIP", name, &vector, "PMD capability");
+ } else if (ret == -ETIMEDOUT) {
+ return -ETIMEDOUT;
+ } else if (ret == -EMSGSIZE) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "message length exceeds mbuf");
+ } else if (ret == -EINVAL) {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "operand wider than curve");
+ } else if (strcmp(vector.result, "valid") == 0) {
+ if (ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS)
+ stats->passed++;
+ else {
+ debug_vector("FAIL", name, &vector,
+ "valid signature rejected");
+ stats->failed++;
+ }
+ } else if (strcmp(vector.result, "invalid") == 0) {
+ if (ret == 0 && status == RTE_CRYPTO_OP_STATUS_SUCCESS) {
+ debug_vector("FAIL", name, &vector,
+ "invalid signature accepted");
+ stats->failed++;
+ } else {
+ stats->passed++;
+ }
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", name, &vector, "unknown result");
+ }
+ rte_free(vector.msg);
+ rte_free(sig);
+ }
+ rte_free(ec.wx);
+ rte_free(ec.wy);
+ }
+ return 0;
+}
+
+static int
+process_file(const char *path, struct app_stats *stats)
+{
+ json_error_t error;
+ json_t *root;
+ const char *algorithm;
+ const char *schema;
+ int ret;
+
+ root = json_load_file(path, 0, &error);
+ if (root == NULL) {
+ if (env.debug)
+ printf("SKIP %s: JSON error at line %d: %s\n", path, error.line,
+ error.text);
+ return 0;
+ }
+ algorithm = json_string_value(json_object_get(root, "algorithm"));
+ schema = json_string_value(json_object_get(root, "schema"));
+ if (algorithm == NULL || schema == NULL) {
+ debug_file("SKIP", path, "missing algorithm or schema");
+ stats->skipped_unsupported++;
+ json_decref(root);
+ return 0;
+ }
+ if (strcmp(algorithm, "AES-GCM") == 0 &&
+ strcmp(schema, "aead_test_schema_v1.json") == 0) {
+ printf("Processing AES-GCM vectors: %s\n", path);
+ ret = process_aead(root, RTE_CRYPTO_AEAD_AES_GCM, "AES-GCM", stats);
+ } else if (strcmp(algorithm, "AES-CCM") == 0 &&
+ strcmp(schema, "aead_test_schema_v1.json") == 0) {
+ printf("Processing AES-CCM vectors: %s\n", path);
+ ret = process_aead(root, RTE_CRYPTO_AEAD_AES_CCM, "AES-CCM", stats);
+ } else if (strcmp(algorithm, "CHACHA20-POLY1305") == 0 &&
+ strcmp(schema, "aead_test_schema_v1.json") == 0) {
+ printf("Processing ChaCha20-Poly1305 vectors: %s\n", path);
+ ret = process_aead(root, RTE_CRYPTO_AEAD_CHACHA20_POLY1305,
+ "ChaCha20-Poly1305", stats);
+ } else if (strcmp(algorithm, "SM4-GCM") == 0 &&
+ strcmp(schema, "aead_test_schema_v1.json") == 0) {
+ printf("Processing SM4-GCM vectors: %s\n", path);
+ ret = process_aead(root, RTE_CRYPTO_AEAD_SM4_GCM, "SM4-GCM", stats);
+ } else if (strcmp(algorithm, "HMACSHA256") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA256 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA256_HMAC, "HMAC-SHA256", stats);
+ } else if (strcmp(algorithm, "HMACSHA1") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA1 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA1_HMAC, "HMAC-SHA1", stats);
+ } else if (strcmp(algorithm, "HMACSHA224") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA224 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA224_HMAC, "HMAC-SHA224", stats);
+ } else if (strcmp(algorithm, "HMACSHA384") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA384 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA384_HMAC, "HMAC-SHA384", stats);
+ } else if (strcmp(algorithm, "HMACSHA512") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA512 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA512_HMAC, "HMAC-SHA512", stats);
+ } else if (strcmp(algorithm, "HMACSHA3-224") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA3-224 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA3_224_HMAC, "HMAC-SHA3-224", stats);
+ } else if (strcmp(algorithm, "HMACSHA3-256") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA3-256 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA3_256_HMAC, "HMAC-SHA3-256", stats);
+ } else if (strcmp(algorithm, "HMACSHA3-384") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA3-384 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA3_384_HMAC, "HMAC-SHA3-384", stats);
+ } else if (strcmp(algorithm, "HMACSHA3-512") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SHA3-512 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SHA3_512_HMAC, "HMAC-SHA3-512", stats);
+ } else if (strcmp(algorithm, "HMACSM3") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing HMAC-SM3 vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_SM3_HMAC, "HMAC-SM3", stats);
+ } else if (strcmp(algorithm, "AES-CMAC") == 0 &&
+ strcmp(schema, "mac_test_schema_v1.json") == 0) {
+ printf("Processing AES-CMAC vectors: %s\n", path);
+ ret = process_hmac(root, RTE_CRYPTO_AUTH_AES_CMAC, "AES-CMAC", stats);
+ } else if (strcmp(algorithm, "AES-GMAC") == 0 &&
+ strcmp(schema, "mac_with_iv_test_schema_v1.json") == 0) {
+ printf("Processing AES-GMAC vectors: %s\n", path);
+ ret = process_gmac(root, stats);
+ } else if (strcmp(algorithm, "DSA") == 0 &&
+ strcmp(schema, "dsa_p1363_verify_schema_v1.json") == 0) {
+ printf("Processing DSA (P1363) vectors: %s\n", path);
+ ret = process_dsa_p1363(root, "DSA", stats);
+ } else if (strcmp(algorithm, "ECDH") == 0 &&
+ strcmp(schema, "ecdh_ecpoint_test_schema_v1.json") == 0) {
+ printf("Processing ECDH (ecpoint) vectors: %s\n", path);
+ ret = process_ecdh_ecpoint(root, "ECDH", stats);
+ } else if (strcmp(algorithm, "ECDSA") == 0 &&
+ strcmp(schema, "ecdsa_p1363_verify_schema_v1.json") == 0) {
+ printf("Processing ECDSA (P1363) vectors: %s\n", path);
+ ret = process_ecdsa_p1363(root, "ECDSA", stats);
+ } else if (strncmp(algorithm, "AES-", strlen("AES-")) == 0 ||
+ strncmp(algorithm, "SEED-", strlen("SEED-")) == 0 ||
+ strcmp(algorithm, "SM4-CCM") == 0 ||
+ strncmp(algorithm, "HMAC", strlen("HMAC")) == 0 ||
+ strncmp(algorithm, "RSA", strlen("RSA")) == 0 ||
+ strcmp(algorithm, "DSA") == 0 ||
+ strcmp(algorithm, "ECDH") == 0 ||
+ strcmp(algorithm, "ECDSA") == 0) {
+ debug_file("SKIP", path, algorithm);
+ stats->skipped_unsupported++;
+ ret = 0;
+ } else {
+ debug_file("SKIP", path, "unsupported vector file");
+ stats->skipped_unsupported++;
+ ret = 0;
+ }
+ json_decref(root);
+ return ret;
+}
+
+static int
+process_path(const char *path, struct app_stats *stats)
+{
+ struct stat path_stat;
+ DIR *directory;
+ struct dirent *entry;
+ int ret = 0;
+
+ if (stat(path, &path_stat) != 0)
+ return -errno;
+ if (!S_ISDIR(path_stat.st_mode))
+ return process_file(path, stats);
+
+ directory = opendir(path);
+ if (directory == NULL)
+ return -errno;
+ while ((entry = readdir(directory)) != NULL) {
+ char file_path[PATH_MAX];
+ size_t name_length = strlen(entry->d_name);
+
+ if (name_length < 6 || strcmp(entry->d_name + name_length - 5, ".json") != 0)
+ continue;
+ if (snprintf(file_path, sizeof(file_path), "%s/%s", path, entry->d_name) >=
+ (int)sizeof(file_path)) {
+ ret = -ENAMETOOLONG;
+ break;
+ }
+ ret = process_file(file_path, stats);
+ if (ret != 0)
+ break;
+ }
+ closedir(directory);
+ return ret;
+}
+
+int
+main(int argc, char **argv)
+{
+ struct app_stats stats = { 0 };
+ int eal_args;
+ int ret;
+
+ eal_args = rte_eal_init(argc, argv);
+ if (eal_args < 0)
+ rte_exit(EXIT_FAILURE, "Cannot initialize EAL\n");
+ argc -= eal_args;
+ argv += eal_args;
+
+ ret = parse_args(argc, argv);
+ if (ret != 0) {
+ usage(argv[0]);
+ ret = EXIT_FAILURE;
+ goto out_eal;
+ }
+ ret = app_init();
+ if (ret != 0) {
+ printf("Cannot initialize cryptodev %u: %s\n", env.dev_id, rte_strerror(-ret));
+ ret = EXIT_FAILURE;
+ goto out_eal;
+ }
+
+ ret = process_path(env.vectors_path, &stats);
+ if (ret != 0)
+ printf("Vector processing failed: %s\n", rte_strerror(-ret));
+ printf("Summary: passed=%" PRIu64 " failed=%" PRIu64
+ " skipped_capability=%" PRIu64 " skipped_acceptable=%" PRIu64
+ " skipped_unsupported=%" PRIu64 "\n", stats.passed, stats.failed,
+ stats.skipped_capability, stats.skipped_acceptable, stats.skipped_unsupported);
+ app_uninit();
+ ret = ret == 0 && stats.failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+
+out_eal:
+ rte_eal_cleanup();
+ return ret;
+}
diff --git a/examples/wycheproof_validation/meson.build b/examples/wycheproof_validation/meson.build
new file mode 100644
index 0000000000..7f18b4cef3
--- /dev/null
+++ b/examples/wycheproof_validation/meson.build
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026
+
+# meson file, for building this example as part of a main DPDK build.
+#
+# To build this example as a standalone application with an already-installed
+# DPDK instance, use 'make'
+
+deps += ['cryptodev']
+
+if not dpdk_conf.has('RTE_HAS_JANSSON')
+ build = false
+ message('Skipping wycheproof_validation: Jansson is required')
+else
+ ext_deps += jansson_dep
+ sources = files('main.c')
+endif
--
2.34.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* RE: [EXTERNAL] [PATCH v7] examples: add Wycheproof validation app
2026-09-23 15:04 ` [PATCH v7] " Kai Ji
@ 2026-09-24 14:35 ` Akhil Goyal
2026-09-24 15:01 ` Ji, Kai
0 siblings, 1 reply; 17+ messages in thread
From: Akhil Goyal @ 2026-09-24 14:35 UTC (permalink / raw)
To: Kai Ji, dev@dpdk.org; +Cc: Thomas Monjalon
> Add a Wycheproof JSON vector validation example for cryptodev PMDs.
>
> Support these algorithms when advertised by the selected PMD:
> - AEAD: AES-GCM, AES-CCM, SM4-GCM, ChaCha20-Poly1305
> - MAC: AES-CMAC, AES-GMAC, HMAC SHA-1/SHA-2/SHA-3/SM3
> - Asymmetric: DSA (P1363 verify), ECDSA (P1363 verify),
> ECDH (ecpoint shared-secret compute)
>
> Validate valid vectors against generated ciphertexts, tags, plaintexts,
> digests, shared secrets, or signature verification, and require the
> expected rejection for invalid vectors. Digest inputs for DSA and ECDSA
> use the symmetric auth path, selecting a separate symmetric-capable
> device when the target device is asymmetric-only.
>
> Skip parameter combinations outside PMD capability ranges and identify
> recognized vector families without a compatible DPDK transform. A
> --debug option lists every failed or skipped vector.
>
> Add Meson and standalone build integration, with usage documentation.
>
> Signed-off-by: Kai Ji <kai.ji@intel.com>
> ---
> MAINTAINERS | 5 +
> doc/guides/rel_notes/release_26_11.rst | 6 +
> doc/guides/sample_app_ug/index.rst | 1 +
> .../sample_app_ug/wycheproof_validation.rst | 50 +
> examples/meson.build | 1 +
> examples/wycheproof_validation/Makefile | 40 +
> examples/wycheproof_validation/main.c | 2090 +++++++++++++++++
> examples/wycheproof_validation/meson.build | 17 +
> 8 files changed, 2210 insertions(+)
> create mode 100644 doc/guides/sample_app_ug/wycheproof_validation.rst
> create mode 100644 examples/wycheproof_validation/Makefile
> create mode 100644 examples/wycheproof_validation/main.c
> create mode 100644 examples/wycheproof_validation/meson.build
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 186cc82b39..580e2c2591 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -2099,3 +2099,8 @@ F: examples/vmdq/
> F: doc/guides/sample_app_ug/vmdq_forwarding.rst
> F: examples/vmdq_dcb/
> F: doc/guides/sample_app_ug/vmdq_dcb_forwarding.rst
> +
> +Wycheproof validation example
> +M: Kai Ji <kai.ji@intel.com>
> +F: examples/wycheproof_validation/
> +F: doc/guides/sample_app_ug/wycheproof_validation.rst
> diff --git a/doc/guides/rel_notes/release_26_11.rst
> b/doc/guides/rel_notes/release_26_11.rst
> index dec96ccbc7..cd2711ddad 100644
> --- a/doc/guides/rel_notes/release_26_11.rst
> +++ b/doc/guides/rel_notes/release_26_11.rst
> @@ -133,6 +133,12 @@ New Features
> with per-descriptor mbuf free (``rte_pktmbuf_free_seg``) and prefetch hints.
> * Changed the set of per-queue xstats counters.
>
> +* **Added Wycheproof validation example application.**
> +
> + Added a new example application that validates a DPDK cryptodev PMD against
> + the Google Wycheproof JSON test vectors, covering AEAD, MAC, DSA, ECDH
> and
> + ECDSA algorithm families.
> +
>
> Removed Items
> -------------
> diff --git a/doc/guides/sample_app_ug/index.rst
> b/doc/guides/sample_app_ug/index.rst
> index 28c4c9a5b2..2458b06ccd 100644
> --- a/doc/guides/sample_app_ug/index.rst
> +++ b/doc/guides/sample_app_ug/index.rst
> @@ -51,6 +51,7 @@ Sample Applications User Guides
> ptpclient
> ptp_tap_relay_sw
> fips_validation
> + wycheproof_validation
> ipsec_secgw
> bbdev_app
> ntb
> diff --git a/doc/guides/sample_app_ug/wycheproof_validation.rst
> b/doc/guides/sample_app_ug/wycheproof_validation.rst
> new file mode 100644
> index 0000000000..17c8bd2b55
> --- /dev/null
> +++ b/doc/guides/sample_app_ug/wycheproof_validation.rst
> @@ -0,0 +1,50 @@
> +.. SPDX-License-Identifier: BSD-3-Clause
> + Copyright(c) 2026 Intel Corporation.
> +
> +Wycheproof Validation Example
> +=============================
> +
> +Overview
> +--------
> +
> +This example validates a DPDK cryptodev implementation against the Google
> +Wycheproof JSON test vectors.
> +
> +The application reads one JSON file or a directory of JSON files at runtime and
> +checks the supported algorithm families against the selected PMD. It can be
> used
> +with a PMD that advertises AEAD, MAC, DSA, ECDH, or ECDSA support.
> +
> +Build
> +-----
> +
> +Build the example from the DPDK tree with Meson:
> +
> +.. code-block:: console
> +
> + meson setup build -Dexamples=wycheproof_validation -
> Denable_drivers=crypto/openssl
> + meson compile -C build
> +
> +Standalone Makefile builds are also supported from the example directory.
> +
> +Run
> +---
> +
> +Run the example with an OpenSSL-backed cryptodev and a vector file:
> +
> +.. code-block:: console
> +
> + ./build/examples/dpdk-wycheproof_validation --vdev crypto_openssl -- \
> + --vectors ../wycheproof/testvectors_v1/aes_gcm_test.json \
> + --cryptodev crypto_openssl --debug
> +
Why do we need a new application to perform test vector validation?
Is dpdk-test crypto tests not enough?
Can we integrate this in that?
And I do not see the .json file mentioned above in the patch.
There should be a sample file atleast.
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [EXTERNAL] [PATCH v7] examples: add Wycheproof validation app
2026-09-24 14:35 ` [EXTERNAL] " Akhil Goyal
@ 2026-09-24 15:01 ` Ji, Kai
2026-09-24 18:00 ` Akhil Goyal
0 siblings, 1 reply; 17+ messages in thread
From: Ji, Kai @ 2026-09-24 15:01 UTC (permalink / raw)
To: Akhil Goyal, dev@dpdk.org; +Cc: Thomas Monjalon
[-- Attachment #1: Type: text/plain, Size: 6433 bytes --]
Wycheproof differs somewhat from the existing cryptodev test vectors in that it is an externally maintained collection of JSON test suites covering a large number of edge cases and negative tests. The intended usage model is to load the upstream vector files at runtime rather than embedding them in the DPDK tree.
I think the existing examples/fips_validation application follows a similar approach, consuming external CAVP/ACVP vector files rather than integrating them into dpdk-test. This patch follows that precedent, with the goal of providing file-driven conformance validation as a standalone example application.
I agree that the documentation can be improved. At minimum, the .rst should describe where the Wycheproof vectors can be obtained and reference the applicable upstream license, as the current example command may give the impression that the vector files are included in the DPDK source tree when they are not.
Regards
Kai
________________________________
From: Akhil Goyal <gakhil@marvell.com>
Sent: Thursday, September 24, 2026 15:35
To: Ji, Kai <kai.ji@intel.com>; dev@dpdk.org <dev@dpdk.org>
Cc: Thomas Monjalon <thomas@monjalon.net>
Subject: RE: [EXTERNAL] [PATCH v7] examples: add Wycheproof validation app
> Add a Wycheproof JSON vector validation example for cryptodev PMDs.
>
> Support these algorithms when advertised by the selected PMD:
> - AEAD: AES-GCM, AES-CCM, SM4-GCM, ChaCha20-Poly1305
> - MAC: AES-CMAC, AES-GMAC, HMAC SHA-1/SHA-2/SHA-3/SM3
> - Asymmetric: DSA (P1363 verify), ECDSA (P1363 verify),
> ECDH (ecpoint shared-secret compute)
>
> Validate valid vectors against generated ciphertexts, tags, plaintexts,
> digests, shared secrets, or signature verification, and require the
> expected rejection for invalid vectors. Digest inputs for DSA and ECDSA
> use the symmetric auth path, selecting a separate symmetric-capable
> device when the target device is asymmetric-only.
>
> Skip parameter combinations outside PMD capability ranges and identify
> recognized vector families without a compatible DPDK transform. A
> --debug option lists every failed or skipped vector.
>
> Add Meson and standalone build integration, with usage documentation.
>
> Signed-off-by: Kai Ji <kai.ji@intel.com>
> ---
> MAINTAINERS | 5 +
> doc/guides/rel_notes/release_26_11.rst | 6 +
> doc/guides/sample_app_ug/index.rst | 1 +
> .../sample_app_ug/wycheproof_validation.rst | 50 +
> examples/meson.build | 1 +
> examples/wycheproof_validation/Makefile | 40 +
> examples/wycheproof_validation/main.c | 2090 +++++++++++++++++
> examples/wycheproof_validation/meson.build | 17 +
> 8 files changed, 2210 insertions(+)
> create mode 100644 doc/guides/sample_app_ug/wycheproof_validation.rst
> create mode 100644 examples/wycheproof_validation/Makefile
> create mode 100644 examples/wycheproof_validation/main.c
> create mode 100644 examples/wycheproof_validation/meson.build
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 186cc82b39..580e2c2591 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -2099,3 +2099,8 @@ F: examples/vmdq/
> F: doc/guides/sample_app_ug/vmdq_forwarding.rst
> F: examples/vmdq_dcb/
> F: doc/guides/sample_app_ug/vmdq_dcb_forwarding.rst
> +
> +Wycheproof validation example
> +M: Kai Ji <kai.ji@intel.com>
> +F: examples/wycheproof_validation/
> +F: doc/guides/sample_app_ug/wycheproof_validation.rst
> diff --git a/doc/guides/rel_notes/release_26_11.rst
> b/doc/guides/rel_notes/release_26_11.rst
> index dec96ccbc7..cd2711ddad 100644
> --- a/doc/guides/rel_notes/release_26_11.rst
> +++ b/doc/guides/rel_notes/release_26_11.rst
> @@ -133,6 +133,12 @@ New Features
> with per-descriptor mbuf free (``rte_pktmbuf_free_seg``) and prefetch hints.
> * Changed the set of per-queue xstats counters.
>
> +* **Added Wycheproof validation example application.**
> +
> + Added a new example application that validates a DPDK cryptodev PMD against
> + the Google Wycheproof JSON test vectors, covering AEAD, MAC, DSA, ECDH
> and
> + ECDSA algorithm families.
> +
>
> Removed Items
> -------------
> diff --git a/doc/guides/sample_app_ug/index.rst
> b/doc/guides/sample_app_ug/index.rst
> index 28c4c9a5b2..2458b06ccd 100644
> --- a/doc/guides/sample_app_ug/index.rst
> +++ b/doc/guides/sample_app_ug/index.rst
> @@ -51,6 +51,7 @@ Sample Applications User Guides
> ptpclient
> ptp_tap_relay_sw
> fips_validation
> + wycheproof_validation
> ipsec_secgw
> bbdev_app
> ntb
> diff --git a/doc/guides/sample_app_ug/wycheproof_validation.rst
> b/doc/guides/sample_app_ug/wycheproof_validation.rst
> new file mode 100644
> index 0000000000..17c8bd2b55
> --- /dev/null
> +++ b/doc/guides/sample_app_ug/wycheproof_validation.rst
> @@ -0,0 +1,50 @@
> +.. SPDX-License-Identifier: BSD-3-Clause
> + Copyright(c) 2026 Intel Corporation.
> +
> +Wycheproof Validation Example
> +=============================
> +
> +Overview
> +--------
> +
> +This example validates a DPDK cryptodev implementation against the Google
> +Wycheproof JSON test vectors.
> +
> +The application reads one JSON file or a directory of JSON files at runtime and
> +checks the supported algorithm families against the selected PMD. It can be
> used
> +with a PMD that advertises AEAD, MAC, DSA, ECDH, or ECDSA support.
> +
> +Build
> +-----
> +
> +Build the example from the DPDK tree with Meson:
> +
> +.. code-block:: console
> +
> + meson setup build -Dexamples=wycheproof_validation -
> Denable_drivers=crypto/openssl
> + meson compile -C build
> +
> +Standalone Makefile builds are also supported from the example directory.
> +
> +Run
> +---
> +
> +Run the example with an OpenSSL-backed cryptodev and a vector file:
> +
> +.. code-block:: console
> +
> + ./build/examples/dpdk-wycheproof_validation --vdev crypto_openssl -- \
> + --vectors ../wycheproof/testvectors_v1/aes_gcm_test.json \
> + --cryptodev crypto_openssl --debug
> +
Why do we need a new application to perform test vector validation?
Is dpdk-test crypto tests not enough?
Can we integrate this in that?
And I do not see the .json file mentioned above in the patch.
There should be a sample file atleast.
[-- Attachment #2: Type: text/html, Size: 10305 bytes --]
^ permalink raw reply [flat|nested] 17+ messages in thread
* RE: [EXTERNAL] [PATCH v7] examples: add Wycheproof validation app
2026-09-24 15:01 ` Ji, Kai
@ 2026-09-24 18:00 ` Akhil Goyal
2026-09-25 16:32 ` Thomas Monjalon
0 siblings, 1 reply; 17+ messages in thread
From: Akhil Goyal @ 2026-09-24 18:00 UTC (permalink / raw)
To: Ji, Kai, dev@dpdk.org; +Cc: Thomas Monjalon
> Wycheproof differs somewhat from the existing cryptodev test vectors in that it is
> an externally maintained collection of JSON test suites covering a large number of
> edge cases and negative tests. The intended usage model is to load the upstream
> vector files at runtime rather than embedding them in the DPDK tree.
> I think the existing examples/fips_validation application follows a similar
> approach, consuming external CAVP/ACVP vector files rather than integrating
> them into dpdk-test. This patch follows that precedent, with the goal of providing
> file-driven conformance validation as a standalone example application.
> I agree that the documentation can be improved. At minimum, the .rst should
> describe where the Wycheproof vectors can be obtained and reference the
> applicable upstream license, as the current example command may give the
> impression that the vector files are included in the DPDK source tree when they
> are not.
Ok, so in that case did you consider integrating this app into fips_validation app..
May be we could rename fips_validation to crypto_validation and internally have 2 modes -
Fips and Wycheproof? Like what we have for examples/multi_process?
>
> From: Akhil Goyal <gakhil@marvell.com>
> Sent: Thursday, September 24, 2026 15:35
> To: Ji, Kai <kai.ji@intel.com>; dev@dpdk.org <dev@dpdk.org>
> Cc: Thomas Monjalon <thomas@monjalon.net>
> Subject: RE: [EXTERNAL] [PATCH v7] examples: add Wycheproof validation app
>
> > Add a Wycheproof JSON vector validation example for cryptodev PMDs.
> >
> > Support these algorithms when advertised by the selected PMD:
> > - AEAD: AES-GCM, AES-CCM, SM4-GCM, ChaCha20-Poly1305
> > - MAC: AES-CMAC, AES-GMAC, HMAC SHA-1/SHA-2/SHA-3/SM3
> > - Asymmetric: DSA (P1363 verify), ECDSA (P1363 verify),
> > ECDH (ecpoint shared-secret compute)
> >
> > Validate valid vectors against generated ciphertexts, tags, plaintexts,
> > digests, shared secrets, or signature verification, and require the
> > expected rejection for invalid vectors. Digest inputs for DSA and ECDSA
> > use the symmetric auth path, selecting a separate symmetric-capable
> > device when the target device is asymmetric-only.
> >
> > Skip parameter combinations outside PMD capability ranges and identify
> > recognized vector families without a compatible DPDK transform. A
> > --debug option lists every failed or skipped vector.
> >
> > Add Meson and standalone build integration, with usage documentation.
> >
> > Signed-off-by: Kai Ji <kai.ji@intel.com>
> > ---
> > MAINTAINERS | 5 +
> > doc/guides/rel_notes/release_26_11.rst | 6 +
> > doc/guides/sample_app_ug/index.rst | 1 +
> > .../sample_app_ug/wycheproof_validation.rst | 50 +
> > examples/meson.build | 1 +
> > examples/wycheproof_validation/Makefile | 40 +
> > examples/wycheproof_validation/main.c | 2090 +++++++++++++++++
> > examples/wycheproof_validation/meson.build | 17 +
> > 8 files changed, 2210 insertions(+)
> > create mode 100644 doc/guides/sample_app_ug/wycheproof_validation.rst
> > create mode 100644 examples/wycheproof_validation/Makefile
> > create mode 100644 examples/wycheproof_validation/main.c
> > create mode 100644 examples/wycheproof_validation/meson.build
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 186cc82b39..580e2c2591 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -2099,3 +2099,8 @@ F: examples/vmdq/
> > F: doc/guides/sample_app_ug/vmdq_forwarding.rst
> > F: examples/vmdq_dcb/
> > F: doc/guides/sample_app_ug/vmdq_dcb_forwarding.rst
> > +
> > +Wycheproof validation example
> > +M: Kai Ji <kai.ji@intel.com>
> > +F: examples/wycheproof_validation/
> > +F: doc/guides/sample_app_ug/wycheproof_validation.rst
> > diff --git a/doc/guides/rel_notes/release_26_11.rst
> > b/doc/guides/rel_notes/release_26_11.rst
> > index dec96ccbc7..cd2711ddad 100644
> > --- a/doc/guides/rel_notes/release_26_11.rst
> > +++ b/doc/guides/rel_notes/release_26_11.rst
> > @@ -133,6 +133,12 @@ New Features
> > with per-descriptor mbuf free (``rte_pktmbuf_free_seg``) and prefetch hints.
> > * Changed the set of per-queue xstats counters.
> >
> > +* **Added Wycheproof validation example application.**
> > +
> > + Added a new example application that validates a DPDK cryptodev PMD
> against
> > + the Google Wycheproof JSON test vectors, covering AEAD, MAC, DSA, ECDH
> > and
> > + ECDSA algorithm families.
> > +
> >
> > Removed Items
> > -------------
> > diff --git a/doc/guides/sample_app_ug/index.rst
> > b/doc/guides/sample_app_ug/index.rst
> > index 28c4c9a5b2..2458b06ccd 100644
> > --- a/doc/guides/sample_app_ug/index.rst
> > +++ b/doc/guides/sample_app_ug/index.rst
> > @@ -51,6 +51,7 @@ Sample Applications User Guides
> > ptpclient
> > ptp_tap_relay_sw
> > fips_validation
> > + wycheproof_validation
> > ipsec_secgw
> > bbdev_app
> > ntb
> > diff --git a/doc/guides/sample_app_ug/wycheproof_validation.rst
> > b/doc/guides/sample_app_ug/wycheproof_validation.rst
> > new file mode 100644
> > index 0000000000..17c8bd2b55
> > --- /dev/null
> > +++ b/doc/guides/sample_app_ug/wycheproof_validation.rst
> > @@ -0,0 +1,50 @@
> > +.. SPDX-License-Identifier: BSD-3-Clause
> > + Copyright(c) 2026 Intel Corporation.
> > +
> > +Wycheproof Validation Example
> > +=============================
> > +
> > +Overview
> > +--------
> > +
> > +This example validates a DPDK cryptodev implementation against the Google
> > +Wycheproof JSON test vectors.
> > +
> > +The application reads one JSON file or a directory of JSON files at runtime and
> > +checks the supported algorithm families against the selected PMD. It can be
> > used
> > +with a PMD that advertises AEAD, MAC, DSA, ECDH, or ECDSA support.
> > +
> > +Build
> > +-----
> > +
> > +Build the example from the DPDK tree with Meson:
> > +
> > +.. code-block:: console
> > +
> > + meson setup build -Dexamples=wycheproof_validation -
> > Denable_drivers=crypto/openssl
> > + meson compile -C build
> > +
> > +Standalone Makefile builds are also supported from the example directory.
> > +
> > +Run
> > +---
> > +
> > +Run the example with an OpenSSL-backed cryptodev and a vector file:
> > +
> > +.. code-block:: console
> > +
> > + ./build/examples/dpdk-wycheproof_validation --vdev crypto_openssl -- \
> > + --vectors ../wycheproof/testvectors_v1/aes_gcm_test.json \
> > + --cryptodev crypto_openssl --debug
> > +
>
> Why do we need a new application to perform test vector validation?
> Is dpdk-test crypto tests not enough?
> Can we integrate this in that?
>
> And I do not see the .json file mentioned above in the patch.
> There should be a sample file atleast.
>
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [EXTERNAL] [PATCH v7] examples: add Wycheproof validation app
2026-09-24 18:00 ` Akhil Goyal
@ 2026-09-25 16:32 ` Thomas Monjalon
2026-09-29 16:12 ` Ji, Kai
0 siblings, 1 reply; 17+ messages in thread
From: Thomas Monjalon @ 2026-09-25 16:32 UTC (permalink / raw)
To: Ji, Kai, Akhil Goyal; +Cc: dev@dpdk.org
24/09/2026 20:00, Akhil Goyal:
> > Wycheproof differs somewhat from the existing cryptodev test vectors in that it is
> > an externally maintained collection of JSON test suites covering a large number of
> > edge cases and negative tests. The intended usage model is to load the upstream
> > vector files at runtime rather than embedding them in the DPDK tree.
> > I think the existing examples/fips_validation application follows a similar
> > approach, consuming external CAVP/ACVP vector files rather than integrating
> > them into dpdk-test. This patch follows that precedent, with the goal of providing
> > file-driven conformance validation as a standalone example application.
> > I agree that the documentation can be improved. At minimum, the .rst should
> > describe where the Wycheproof vectors can be obtained and reference the
> > applicable upstream license, as the current example command may give the
> > impression that the vector files are included in the DPDK source tree when they
> > are not.
>
> Ok, so in that case did you consider integrating this app into fips_validation app..
> May be we could rename fips_validation to crypto_validation and internally have 2 modes -
> Fips and Wycheproof? Like what we have for examples/multi_process?
I like this proposal.
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [EXTERNAL] [PATCH v7] examples: add Wycheproof validation app
2026-09-25 16:32 ` Thomas Monjalon
@ 2026-09-29 16:12 ` Ji, Kai
2026-09-29 18:21 ` Akhil Goyal
0 siblings, 1 reply; 17+ messages in thread
From: Ji, Kai @ 2026-09-29 16:12 UTC (permalink / raw)
To: Thomas Monjalon, Akhil Goyal; +Cc: dev@dpdk.org
[-- Attachment #1: Type: text/plain, Size: 2730 bytes --]
I agree that a single validation framework would make cryptodev validation easier to discover, document, and maintain, and could eventually allow some common setup and reporting infrastructure to be shared.
That said, the overlap between the two applications is primarily at the cryptodev API level. Their core purposes are quite different. The FIPS validation application is focused on CAVP/ACVP workflows, including request/response processing and conformance testing, whereas Wycheproof is built around externally maintained JSON test suites with valid/invalid/acceptable result classifications and different requirements for asymmetric devices.
While there may be opportunities for code reuse in the future, merging them today would likely introduce additional dispatcher and lifecycle complexity with limited immediate benefit. It could also blur the distinction between Wycheproof robustness testing and FIPS conformance validation.
My preference would be to keep them as separate applications for now and revisit consolidation if we identify a meaningful amount of shared infrastructure.
Regards,
Kai
________________________________
From: Thomas Monjalon <thomas@monjalon.net>
Sent: Friday, September 25, 2026 17:32
To: Ji, Kai <kai.ji@intel.com>; Akhil Goyal <gakhil@marvell.com>
Cc: dev@dpdk.org <dev@dpdk.org>
Subject: Re: [EXTERNAL] [PATCH v7] examples: add Wycheproof validation app
24/09/2026 20:00, Akhil Goyal:
> > Wycheproof differs somewhat from the existing cryptodev test vectors in that it is
> > an externally maintained collection of JSON test suites covering a large number of
> > edge cases and negative tests. The intended usage model is to load the upstream
> > vector files at runtime rather than embedding them in the DPDK tree.
> > I think the existing examples/fips_validation application follows a similar
> > approach, consuming external CAVP/ACVP vector files rather than integrating
> > them into dpdk-test. This patch follows that precedent, with the goal of providing
> > file-driven conformance validation as a standalone example application.
> > I agree that the documentation can be improved. At minimum, the .rst should
> > describe where the Wycheproof vectors can be obtained and reference the
> > applicable upstream license, as the current example command may give the
> > impression that the vector files are included in the DPDK source tree when they
> > are not.
>
> Ok, so in that case did you consider integrating this app into fips_validation app..
> May be we could rename fips_validation to crypto_validation and internally have 2 modes -
> Fips and Wycheproof? Like what we have for examples/multi_process?
I like this proposal.
[-- Attachment #2: Type: text/html, Size: 4890 bytes --]
^ permalink raw reply [flat|nested] 17+ messages in thread
* RE: [EXTERNAL] [PATCH v7] examples: add Wycheproof validation app
2026-09-29 16:12 ` Ji, Kai
@ 2026-09-29 18:21 ` Akhil Goyal
0 siblings, 0 replies; 17+ messages in thread
From: Akhil Goyal @ 2026-09-29 18:21 UTC (permalink / raw)
To: Ji, Kai, Thomas Monjalon; +Cc: dev@dpdk.org
> I agree that a single validation framework would make cryptodev validation easier
> to discover, document, and maintain, and could eventually allow some common
> setup and reporting infrastructure to be shared.
> That said, the overlap between the two applications is primarily at the cryptodev
> API level. Their core purposes are quite different. The FIPS validation application is
> focused on CAVP/ACVP workflows, including request/response processing and
> conformance testing, whereas Wycheproof is built around externally maintained
> JSON test suites with valid/invalid/acceptable result classifications and different
> requirements for asymmetric devices.
> While there may be opportunities for code reuse in the future, merging them
> today would likely introduce additional dispatcher and lifecycle complexity with
> limited immediate benefit. It could also blur the distinction between Wycheproof
> robustness testing and FIPS conformance validation.
> My preference would be to keep them as separate applications for now and
> revisit consolidation if we identify a meaningful amount of shared infrastructure.
The idea is not about code reuse.. But it is to limit the number of example applications.
For crypto validation we already have fips_validation as well as dpdk-test.
If we move this new app under a folder examples/crypto_validation along with fips_validation,
it will make more sense like multi_process example.
We cannot just keep on adding new examples for every new way of crypto validation.
^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2026-09-29 18:21 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-15 15:51 [PATCH] examples: add Wycheproof validation app Kai Ji
2026-09-15 16:58 ` Stephen Hemminger
2026-09-15 18:28 ` Stephen Hemminger
2026-09-17 15:34 ` [PATCH v3] " Kai Ji
2026-09-21 12:05 ` [EXTERNAL] " Gowrishankar Muthukrishnan
2026-09-21 16:41 ` Stephen Hemminger
2026-09-22 14:47 ` [PATCH v4] " Kai Ji
2026-09-22 16:05 ` [PATCH v5] " Kai Ji
2026-09-22 16:26 ` Stephen Hemminger
2026-09-23 10:50 ` [PATCH v6] " Kai Ji
2026-09-23 15:04 ` [PATCH v7] " Kai Ji
2026-09-24 14:35 ` [EXTERNAL] " Akhil Goyal
2026-09-24 15:01 ` Ji, Kai
2026-09-24 18:00 ` Akhil Goyal
2026-09-25 16:32 ` Thomas Monjalon
2026-09-29 16:12 ` Ji, Kai
2026-09-29 18:21 ` Akhil Goyal
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox