From: Kai Ji <kai.ji@intel.com>
To: dev@dpdk.org
Cc: Kai Ji <kai.ji@intel.com>, Thomas Monjalon <thomas@monjalon.net>,
Gowrishankar Muthukrishnan <gmuthukrishn@marvell.com>
Subject: [dpdk-dev v11 2/2] app: add Wycheproof validation app
Date: Fri, 6 Nov 2026 16:14:41 +0000 [thread overview]
Message-ID: <20261106161441.3818963-3-kai.ji@intel.com> (raw)
In-Reply-To: <20261106161441.3818963-1-kai.ji@intel.com>
Add a cryptodev validation application using Wycheproof JSON test
vectors. Document it as a second section in the crypto validation guide.
Signed-off-by: Kai Ji <kai.ji@intel.com>
---
MAINTAINERS | 5 +
app/meson.build | 1 +
app/wycheproof_validation/main.c | 2106 +++++++++++++++++
app/wycheproof_validation/meson.build | 20 +
doc/guides/rel_notes/release_26_11.rst | 6 +
.../sample_app_ug/crypto_validation.rst | 50 +
6 files changed, 2188 insertions(+)
create mode 100644 app/wycheproof_validation/main.c
create mode 100644 app/wycheproof_validation/meson.build
diff --git a/MAINTAINERS b/MAINTAINERS
index 736f39c70e..d258f4cea4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2116,3 +2116,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 application
+M: Kai Ji <kai.ji@intel.com>
+F: app/wycheproof_validation/
+F: doc/guides/sample_app_ug/crypto_validation.rst
diff --git a/app/meson.build b/app/meson.build
index 927f79d0b3..6239e05093 100644
--- a/app/meson.build
+++ b/app/meson.build
@@ -34,6 +34,7 @@ apps = [
'test-sad',
'test-security-perf',
'validate-bpf',
+ 'wycheproof_validation',
]
if get_option('tests')
diff --git a/app/wycheproof_validation/main.c b/app/wycheproof_validation/main.c
new file mode 100644
index 0000000000..9b8182f15b
--- /dev/null
+++ b/app/wycheproof_validation/main.c
@@ -0,0 +1,2106 @@
+/* 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>
+#include <rte_memory.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]\n"
+ "\t[--mbuf-dataroom BYTES] [--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_sensitive(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 if (strcmp(vector.result, "acceptable") == 0) {
+ stats->skipped_acceptable++;
+ debug_vector("SKIP", "AES-GMAC", &vector,
+ "acceptable result policy");
+ ret = 0;
+ } else {
+ stats->skipped_unsupported++;
+ debug_vector("SKIP", "AES-GMAC", &vector, "unknown result");
+ 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:
+ /* The private key was copied into the stack buffer; wipe it. */
+ rte_memzero_explicit(priv_buf, sizeof(priv_buf));
+ 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_sensitive(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;
+ }
+ if (ret != 0) {
+ debug_vector("FAIL", name, &vector, "shared-secret computation failed");
+ stats->failed++;
+ 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_sensitive(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 (ret != 0) {
+ debug_vector("FAIL", name, &vector, "signature verification failed");
+ stats->failed++;
+ } 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) {
+ printf("Cannot load %s: JSON error at line %d: %s\n", path, error.line,
+ error.text);
+ return -EINVAL;
+ }
+ 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 };
+ const char *prgname = argv[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(prgname);
+ 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/app/wycheproof_validation/meson.build b/app/wycheproof_validation/meson.build
new file mode 100644
index 0000000000..e032144bb4
--- /dev/null
+++ b/app/wycheproof_validation/meson.build
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026
+
+# meson file, for building this application as part of a main DPDK build.
+
+if is_windows
+ build = false
+ reason = 'not supported on Windows'
+ subdir_done()
+endif
+
+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
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 8ca8379cc6..20304c6a5f 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -340,6 +340,12 @@ New Features
Added ``dpdk-validate-bpf`` tool to pre-validate eBPF programs
for compatibility with the ``lib/bpf`` execution context.
+* **Added Wycheproof validation application.**
+
+ Added a new 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/crypto_validation.rst b/doc/guides/sample_app_ug/crypto_validation.rst
index 67b9e2d351..a1a885cb6e 100644
--- a/doc/guides/sample_app_ug/crypto_validation.rst
+++ b/doc/guides/sample_app_ug/crypto_validation.rst
@@ -170,3 +170,53 @@ data files in one folder for crypto_aesni_gcm PMD, issue the command:
--req-file /PATH/TO/REQUEST/FILE/FOLDER/
--rsp-file ./PATH/TO/RESPONSE/FILE/FOLDER/
--cryptodev-id 0 --path-is-folder
+
+Wycheproof Validation
+---------------------
+
+Overview
+~~~~~~~~
+
+This application 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 application from the DPDK tree with Meson:
+
+.. code-block:: console
+
+ meson setup build -Denable_apps=wycheproof_validation \
+ -Denable_drivers=crypto/openssl
+ meson compile -C build
+
+Run
+~~~
+
+Run the application with an OpenSSL-backed cryptodev and a vector file:
+
+.. code-block:: console
+
+ ./build/app/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
+``--mbuf-dataroom`` option sets the mbuf data room size in bytes
+(default 16384); vectors with messages larger than the data room minus the
+mbuf headroom are skipped, so increase it to cover larger messages.
+
+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.
--
2.34.1
next prev parent reply other threads:[~2026-11-06 16:15 UTC|newest]
Thread overview: 29+ messages / expand[flat|nested] mbox.gz Atom feed top
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
2026-10-13 15:20 ` [PATCH v8] " Kai Ji
2026-10-13 15:43 ` [PATCH v9] " Kai Ji
2026-10-26 18:21 ` [EXTERNAL] " Akhil Goyal
2026-10-28 5:52 ` Akhil Goyal
2026-11-05 16:31 ` [dpdk-dev v10 0/2] app: add cryptodev validation applications Kai Ji
2026-11-05 16:31 ` [dpdk-dev v10 1/2] app: move FIPS validation under app/ Kai Ji
2026-11-05 16:31 ` [dpdk-dev v10 2/2] app: add Wycheproof validation app Kai Ji
2026-11-06 16:14 ` [dpdk-dev v11 0/2] app: add cryptodev validation applications Kai Ji
2026-11-06 16:14 ` [dpdk-dev v11 1/2] app: move FIPS validation under app/ Kai Ji
2026-11-09 16:00 ` Ji, Kai
2026-11-06 16:14 ` Kai Ji [this message]
2026-11-12 18:42 ` [dpdk-dev v11 0/2] app: add cryptodev validation applications Thomas Monjalon
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261106161441.3818963-3-kai.ji@intel.com \
--to=kai.ji@intel.com \
--cc=dev@dpdk.org \
--cc=gmuthukrishn@marvell.com \
--cc=thomas@monjalon.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox