mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-10-24 04:09:07 +03:00
A crypto callback that returns WC_PENDING_E for a TLS record cipher silently corrupted records: Encrypt()/Decrypt() advance the cipher state to CIPHER_STATE_END before the pending check, so on resume the record is shipped without re-running the cipher. WOLF_CRYPTO_CB_ASYNC_POLL gives crypto callback devices the QAT/Nitrox "poll to fill output" completion model. On WC_PENDING_E the async event stays queued; wolfSSL_AsyncPoll() re-enters the device with the new WC_ALGO_TYPE_ASYNC_POLL (wc_CryptoCb_Poll) to finish the job and fill the output buffer. The re-entry only polls while the event is still pending, and a device that cannot complete the job (no poll support, or it reports nothing pending) hard-fails with WC_HW_E rather than reporting the op done with an unfilled buffer. Only the two async record ciphers (AES and 3DES markers) are routed to poll completion, and only when crypto callbacks are the async backend (not QAT/Cavium/SW); handshake PK keeps the re-invoke model. The wolfSSL_AsyncPop eviction is gated for poll-capable devices so the existing resume-at-CIPHER_STATE_END path becomes correct with no record state-machine changes. Without the feature (and without a software/QAT/Cavium backend) a pending bulk cipher op now errors out with ASYNC_OP_E instead of corrupting the record, and configure/cryptocb.c warn about the unsupported combination. Tests in tests/api/test_async.c cover direct AES-GCM/CBC/CCM and 3DES poll completion in both directions at multiple pend depths, negative cases for cipher types defined but not dispatched (ChaCha, single DES), and full TLS 1.3 handshake+echo: an encrypt-offload run, a both- directions run that offloads encrypt and decrypt on both peers using a per-peer device, and the no-poll failure path.
1039 lines
36 KiB
C
1039 lines
36 KiB
C
/* test_async.c
|
|
*
|
|
* Copyright (C) 2006-2026 wolfSSL Inc.
|
|
*
|
|
* This file is part of wolfSSL.
|
|
*
|
|
* wolfSSL is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* wolfSSL is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
|
|
*/
|
|
|
|
#include <tests/unit.h>
|
|
|
|
#ifdef NO_INLINE
|
|
#include <wolfssl/wolfcrypt/misc.h>
|
|
#else
|
|
#define WOLFSSL_MISC_INCLUDED
|
|
#include <wolfcrypt/src/misc.c>
|
|
#endif
|
|
|
|
#include <wolfssl/wolfcrypt/settings.h>
|
|
#include <wolfssl/wolfcrypt/error-crypt.h>
|
|
#include <wolfssl/wolfcrypt/cryptocb.h>
|
|
#include <wolfssl/wolfcrypt/aes.h>
|
|
#include <wolfssl/wolfcrypt/des3.h>
|
|
#include <wolfssl/wolfcrypt/chacha20_poly1305.h>
|
|
#include <wolfssl/wolfcrypt/async.h>
|
|
#include <wolfssl/wolfcrypt/wolfevent.h>
|
|
#include <tests/api/api.h>
|
|
#include <tests/api/test_async.h>
|
|
#include <tests/utils.h>
|
|
|
|
/*
|
|
* Crypto callback async "poll to fill output" completion
|
|
* (WOLF_CRYPTO_CB_ASYNC_POLL).
|
|
*
|
|
* A crypto callback that returns WC_PENDING_E for a TLS record cipher is
|
|
* completed at poll time: wolfSSL keeps the async event queued and re-enters
|
|
* the callback with WC_ALGO_TYPE_ASYNC_POLL so it can finish the job and fill
|
|
* the output buffer. These tests exercise the completion mechanism directly
|
|
* with a fake HSM that offloads the record ciphers (AES-GCM, AES-CBC,
|
|
* AES-CCM, 3DES-CBC) and declines everything else.
|
|
*
|
|
* The tests only run when the feature is compiled in; otherwise they skip.
|
|
*/
|
|
|
|
#if defined(WOLFSSL_ASYNC_CRYPT) && defined(WOLF_CRYPTO_CB) && \
|
|
defined(WOLF_CRYPTO_CB_ASYNC_POLL)
|
|
|
|
#define TEST_ASYNC_HSM_DEVID 0x00005150
|
|
#define TEST_ASYNC_HSM_DEVID2 0x00005151 /* second device, per-peer TLS test */
|
|
#define TEST_ASYNC_MAX_POLLS 16
|
|
|
|
/* Offloading tests run at pend depths 0..TEST_ASYNC_MAX_PEND. Depth N returns
|
|
* WC_PENDING_E on submit and N more times at poll before completing (N+1 polls
|
|
* total). Must stay below TEST_ASYNC_MAX_POLLS. */
|
|
#define TEST_ASYNC_MAX_PEND 5
|
|
|
|
/* One in-flight offloaded job. */
|
|
typedef struct TestHsmJob {
|
|
int active;
|
|
int type; /* wc_CipherType */
|
|
int dec; /* 0 = encrypt, 1 = decrypt */
|
|
int pendRemaining; /* polls still returning WC_PENDING_E */
|
|
void* obj; /* Aes* or Des3* */
|
|
byte* out;
|
|
const byte* in;
|
|
word32 sz;
|
|
const byte* iv;
|
|
word32 ivSz;
|
|
byte* authTag; /* encrypt: output tag */
|
|
const byte* authTagDec; /* decrypt: input tag to verify */
|
|
word32 authTagSz;
|
|
const byte* authIn;
|
|
word32 authInSz;
|
|
} TestHsmJob;
|
|
|
|
typedef struct TestHsm {
|
|
TestHsmJob job;
|
|
int pendPolls; /* polls to stay in-flight before completing */
|
|
int offloadDec; /* also offload decrypt (single-op tests only) */
|
|
int invocations; /* callback entered at all (any algo/type) */
|
|
int submits; /* offloaded (pended) op count */
|
|
int decSubmits; /* offloaded decrypt op count */
|
|
int polls;
|
|
} TestHsm;
|
|
|
|
/* Run the software cipher into the saved output, with routing disabled so the
|
|
* completion does not re-enter this callback. */
|
|
static int test_hsm_complete(TestHsmJob* j)
|
|
{
|
|
int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
|
|
|
|
switch (j->type) {
|
|
#ifdef HAVE_AESGCM
|
|
case WC_CIPHER_AES_GCM: {
|
|
Aes* aes = (Aes*)j->obj;
|
|
int save = aes->devId;
|
|
aes->devId = INVALID_DEVID;
|
|
if (j->dec)
|
|
ret = wc_AesGcmDecrypt(aes, j->out, j->in, j->sz, j->iv, j->ivSz,
|
|
j->authTagDec, j->authTagSz, j->authIn,
|
|
j->authInSz);
|
|
else
|
|
ret = wc_AesGcmEncrypt(aes, j->out, j->in, j->sz, j->iv, j->ivSz,
|
|
j->authTag, j->authTagSz, j->authIn,
|
|
j->authInSz);
|
|
aes->devId = save;
|
|
break;
|
|
}
|
|
#endif
|
|
#ifdef HAVE_AESCCM
|
|
case WC_CIPHER_AES_CCM: {
|
|
Aes* aes = (Aes*)j->obj;
|
|
int save = aes->devId;
|
|
aes->devId = INVALID_DEVID;
|
|
if (j->dec)
|
|
ret = wc_AesCcmDecrypt(aes, j->out, j->in, j->sz, j->iv, j->ivSz,
|
|
j->authTagDec, j->authTagSz, j->authIn,
|
|
j->authInSz);
|
|
else
|
|
ret = wc_AesCcmEncrypt(aes, j->out, j->in, j->sz, j->iv, j->ivSz,
|
|
j->authTag, j->authTagSz, j->authIn,
|
|
j->authInSz);
|
|
aes->devId = save;
|
|
break;
|
|
}
|
|
#endif
|
|
#ifdef HAVE_AES_CBC
|
|
case WC_CIPHER_AES_CBC: {
|
|
Aes* aes = (Aes*)j->obj;
|
|
int save = aes->devId;
|
|
aes->devId = INVALID_DEVID;
|
|
if (j->dec)
|
|
ret = wc_AesCbcDecrypt(aes, j->out, j->in, j->sz);
|
|
else
|
|
ret = wc_AesCbcEncrypt(aes, j->out, j->in, j->sz);
|
|
aes->devId = save;
|
|
break;
|
|
}
|
|
#endif
|
|
#ifndef NO_DES3
|
|
case WC_CIPHER_DES3: {
|
|
Des3* des = (Des3*)j->obj;
|
|
int save = des->devId;
|
|
des->devId = INVALID_DEVID;
|
|
if (j->dec)
|
|
ret = wc_Des3_CbcDecrypt(des, j->out, j->in, j->sz);
|
|
else
|
|
ret = wc_Des3_CbcEncrypt(des, j->out, j->in, j->sz);
|
|
des->devId = save;
|
|
break;
|
|
}
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* Fake HSM: pend and poll-complete the record ciphers, decline everything
|
|
* else so it falls back to software. */
|
|
static int test_hsm_cb(int devId, wc_CryptoInfo* info, void* ctx)
|
|
{
|
|
TestHsm* hsm = (TestHsm*)ctx;
|
|
(void)devId;
|
|
|
|
if (info == NULL)
|
|
return WC_NO_ERR_TRACE(BAD_FUNC_ARG);
|
|
|
|
hsm->invocations++;
|
|
|
|
if (info->algo_type == WC_ALGO_TYPE_CIPHER) {
|
|
TestHsmJob* j = &hsm->job;
|
|
|
|
/* Offload decrypt only when asked. The single job slot cannot hold a
|
|
* client decrypt and a server encrypt at once, which collides in the
|
|
* TLS half-duplex flow; the direct single-op tests drive decrypt on
|
|
* its own instead. */
|
|
if (info->cipher.enc == 0 && !hsm->offloadDec)
|
|
return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
|
|
|
|
XMEMSET(j, 0, sizeof(*j));
|
|
j->dec = (info->cipher.enc == 0);
|
|
|
|
switch (info->cipher.type) {
|
|
#ifdef HAVE_AESGCM
|
|
case WC_CIPHER_AES_GCM:
|
|
if (j->dec) {
|
|
wc_CryptoCb_AesAuthDec* e = &info->cipher.aesgcm_dec;
|
|
j->obj = e->aes; j->out = e->out; j->in = e->in; j->sz = e->sz;
|
|
j->iv = e->iv; j->ivSz = e->ivSz;
|
|
j->authTagDec = e->authTag; j->authTagSz = e->authTagSz;
|
|
j->authIn = e->authIn; j->authInSz = e->authInSz;
|
|
}
|
|
else {
|
|
wc_CryptoCb_AesAuthEnc* e = &info->cipher.aesgcm_enc;
|
|
j->obj = e->aes; j->out = e->out; j->in = e->in; j->sz = e->sz;
|
|
j->iv = e->iv; j->ivSz = e->ivSz;
|
|
j->authTag = e->authTag; j->authTagSz = e->authTagSz;
|
|
j->authIn = e->authIn; j->authInSz = e->authInSz;
|
|
}
|
|
break;
|
|
#endif
|
|
#ifdef HAVE_AESCCM
|
|
case WC_CIPHER_AES_CCM: /* CCM uses the nonce field */
|
|
if (j->dec) {
|
|
wc_CryptoCb_AesAuthDec* e = &info->cipher.aesccm_dec;
|
|
j->obj = e->aes; j->out = e->out; j->in = e->in; j->sz = e->sz;
|
|
j->iv = e->nonce; j->ivSz = e->nonceSz;
|
|
j->authTagDec = e->authTag; j->authTagSz = e->authTagSz;
|
|
j->authIn = e->authIn; j->authInSz = e->authInSz;
|
|
}
|
|
else {
|
|
wc_CryptoCb_AesAuthEnc* e = &info->cipher.aesccm_enc;
|
|
j->obj = e->aes; j->out = e->out; j->in = e->in; j->sz = e->sz;
|
|
j->iv = e->nonce; j->ivSz = e->nonceSz;
|
|
j->authTag = e->authTag; j->authTagSz = e->authTagSz;
|
|
j->authIn = e->authIn; j->authInSz = e->authInSz;
|
|
}
|
|
break;
|
|
#endif
|
|
#ifdef HAVE_AES_CBC
|
|
case WC_CIPHER_AES_CBC:
|
|
j->obj = info->cipher.aescbc.aes; j->out = info->cipher.aescbc.out;
|
|
j->in = info->cipher.aescbc.in; j->sz = info->cipher.aescbc.sz;
|
|
break;
|
|
#endif
|
|
#ifndef NO_DES3
|
|
case WC_CIPHER_DES3:
|
|
j->obj = info->cipher.des3.des; j->out = info->cipher.des3.out;
|
|
j->in = info->cipher.des3.in; j->sz = info->cipher.des3.sz;
|
|
break;
|
|
#endif
|
|
default:
|
|
/* not a record cipher this HSM offloads: run in software */
|
|
return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
|
|
}
|
|
|
|
j->active = 1;
|
|
j->type = info->cipher.type;
|
|
j->pendRemaining = hsm->pendPolls;
|
|
hsm->submits++;
|
|
if (j->dec)
|
|
hsm->decSubmits++;
|
|
return WC_PENDING_E;
|
|
}
|
|
|
|
if (info->algo_type == WC_ALGO_TYPE_ASYNC_POLL) {
|
|
TestHsmJob* j = &hsm->job;
|
|
hsm->polls++;
|
|
if (!j->active)
|
|
return WC_NO_ERR_TRACE(WC_NO_PENDING_E);
|
|
if (j->pendRemaining > 0) {
|
|
j->pendRemaining--; /* still in-flight this poll */
|
|
return WC_PENDING_E;
|
|
}
|
|
j->active = 0;
|
|
return test_hsm_complete(j);
|
|
}
|
|
|
|
return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
|
|
}
|
|
|
|
/* Reset the device counters and in-flight job for another run, and set how
|
|
* many polls the next op stays in-flight (pendPolls) before completing. */
|
|
static void test_hsm_reset(TestHsm* hsm, int pendPolls)
|
|
{
|
|
XMEMSET(&hsm->job, 0, sizeof(hsm->job));
|
|
hsm->pendPolls = pendPolls;
|
|
hsm->invocations = 0;
|
|
hsm->submits = 0;
|
|
hsm->decSubmits = 0;
|
|
hsm->polls = 0;
|
|
}
|
|
|
|
/* Init the event and drive the poll to completion, bounded so a missing poll
|
|
* path fails instead of hanging. Returns the event result. */
|
|
static int test_async_drive_poll(WC_ASYNC_DEV* dev, int ret)
|
|
{
|
|
if (ret == WC_NO_ERR_TRACE(WC_PENDING_E)) {
|
|
WOLF_EVENT* ev = &dev->event;
|
|
int i;
|
|
(void)wolfAsync_EventInit(ev, WOLF_EVENT_TYPE_ASYNC_WOLFCRYPT, dev,
|
|
WC_ASYNC_FLAG_NONE);
|
|
for (i = 0; i < TEST_ASYNC_MAX_POLLS &&
|
|
ev->ret == WC_NO_ERR_TRACE(WC_PENDING_E); i++) {
|
|
(void)wolfAsync_EventPoll(ev, WOLF_POLL_FLAG_CHECK_HW);
|
|
}
|
|
ret = ev->ret;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static WC_INLINE const byte* test_async_key32(void)
|
|
{
|
|
static const byte k[32] = {
|
|
0x60,0x3d,0xeb,0x10,0x15,0xca,0x71,0xbe,
|
|
0x2b,0x73,0xae,0xf0,0x85,0x7d,0x77,0x81,
|
|
0x1f,0x35,0x2c,0x07,0x3b,0x61,0x08,0xd7,
|
|
0x2d,0x98,0x10,0xa3,0x09,0x14,0xdf,0xf4
|
|
};
|
|
return k;
|
|
}
|
|
static WC_INLINE const byte* test_async_iv12(void)
|
|
{
|
|
static const byte v[12] = {
|
|
0xca,0xfe,0xba,0xbe,0xfa,0xce,0xdb,0xad,0xde,0xca,0xf8,0x88
|
|
};
|
|
return v;
|
|
}
|
|
static WC_INLINE const byte* test_async_iv16(void)
|
|
{
|
|
static const byte v[16] = {
|
|
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
|
|
0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f
|
|
};
|
|
return v;
|
|
}
|
|
static WC_INLINE const byte* test_async_plain32(void)
|
|
{
|
|
static const byte p[32] = {
|
|
0xd9,0x31,0x32,0x25,0xf8,0x84,0x06,0xe5,
|
|
0xa5,0x59,0x09,0xc5,0xaf,0xf5,0x26,0x9a,
|
|
0x86,0xa7,0xa9,0x53,0x15,0x34,0xf7,0xda,
|
|
0x2e,0x4c,0x30,0x3d,0x8a,0x31,0x8a,0x72
|
|
};
|
|
return p;
|
|
}
|
|
static WC_INLINE const byte* test_async_aad12(void)
|
|
{
|
|
static const byte a[12] = {
|
|
0xfe,0xed,0xfa,0xce,0xde,0xad,0xbe,0xef,0xfe,0xed,0xfa,0xce
|
|
};
|
|
return a;
|
|
}
|
|
|
|
#endif /* feature macros */
|
|
|
|
/*
|
|
* AES-GCM record cipher: pends, poll-completes, and produces the same
|
|
* ciphertext and tag as a pure software encrypt.
|
|
*/
|
|
int test_wc_CryptoCb_AsyncPollAesGcm(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(WOLFSSL_ASYNC_CRYPT) && defined(WOLF_CRYPTO_CB) && \
|
|
defined(WOLF_CRYPTO_CB_ASYNC_POLL) && defined(HAVE_AESGCM)
|
|
TestHsm hsm;
|
|
Aes hw, sw;
|
|
byte out[32], ref[32], tag[16], rtag[16];
|
|
int registered = 0;
|
|
int pend;
|
|
|
|
XMEMSET(&hsm, 0, sizeof(hsm));
|
|
XMEMSET(&hw, 0, sizeof(hw));
|
|
XMEMSET(&sw, 0, sizeof(sw));
|
|
|
|
ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_ASYNC_HSM_DEVID, test_hsm_cb,
|
|
&hsm), 0);
|
|
if (EXPECT_SUCCESS())
|
|
registered = 1;
|
|
|
|
ExpectIntEQ(wc_AesInit(&hw, NULL, TEST_ASYNC_HSM_DEVID), 0);
|
|
ExpectIntEQ(wc_AesInit(&sw, NULL, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_AesGcmSetKey(&hw, test_async_key32(), 32), 0);
|
|
ExpectIntEQ(wc_AesGcmSetKey(&sw, test_async_key32(), 32), 0);
|
|
|
|
/* software reference */
|
|
ExpectIntEQ(wc_AesGcmEncrypt(&sw, ref, test_async_plain32(), 32,
|
|
test_async_iv12(), 12, rtag, 16, test_async_aad12(), 12), 0);
|
|
|
|
/* pend depth 0..TEST_ASYNC_MAX_PEND: op completes on poll number pend+1 */
|
|
for (pend = 0; pend <= TEST_ASYNC_MAX_PEND; pend++) {
|
|
test_hsm_reset(&hsm, pend);
|
|
ExpectIntEQ(test_async_drive_poll(&hw.asyncDev,
|
|
wc_AesGcmEncrypt(&hw, out, test_async_plain32(), 32,
|
|
test_async_iv12(), 12, tag, 16, test_async_aad12(), 12)), 0);
|
|
ExpectIntEQ(hsm.submits, 1);
|
|
ExpectIntEQ(hsm.polls, pend + 1);
|
|
ExpectBufEQ(out, ref, 32);
|
|
ExpectBufEQ(tag, rtag, 16);
|
|
}
|
|
|
|
/* decrypt side: poll-complete recovers the plaintext (same GCM key) */
|
|
hsm.offloadDec = 1;
|
|
for (pend = 0; pend <= TEST_ASYNC_MAX_PEND; pend++) {
|
|
test_hsm_reset(&hsm, pend);
|
|
ExpectIntEQ(test_async_drive_poll(&hw.asyncDev,
|
|
wc_AesGcmDecrypt(&hw, out, ref, 32,
|
|
test_async_iv12(), 12, rtag, 16, test_async_aad12(), 12)), 0);
|
|
ExpectIntEQ(hsm.submits, 1);
|
|
ExpectIntEQ(hsm.polls, pend + 1);
|
|
ExpectBufEQ(out, test_async_plain32(), 32);
|
|
}
|
|
|
|
wc_AesFree(&hw);
|
|
wc_AesFree(&sw);
|
|
if (registered)
|
|
wc_CryptoCb_UnRegisterDevice(TEST_ASYNC_HSM_DEVID);
|
|
#endif
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*
|
|
* AES-CBC record cipher: pends, poll-completes, matches software.
|
|
*/
|
|
int test_wc_CryptoCb_AsyncPollAesCbc(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(WOLFSSL_ASYNC_CRYPT) && defined(WOLF_CRYPTO_CB) && \
|
|
defined(WOLF_CRYPTO_CB_ASYNC_POLL) && defined(HAVE_AES_CBC)
|
|
TestHsm hsm;
|
|
Aes hw, sw, hwd;
|
|
byte out[32], ref[32];
|
|
int registered = 0;
|
|
int pend;
|
|
|
|
XMEMSET(&hsm, 0, sizeof(hsm));
|
|
XMEMSET(&hw, 0, sizeof(hw));
|
|
XMEMSET(&sw, 0, sizeof(sw));
|
|
XMEMSET(&hwd, 0, sizeof(hwd));
|
|
|
|
ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_ASYNC_HSM_DEVID, test_hsm_cb,
|
|
&hsm), 0);
|
|
if (EXPECT_SUCCESS())
|
|
registered = 1;
|
|
|
|
ExpectIntEQ(wc_AesInit(&hw, NULL, TEST_ASYNC_HSM_DEVID), 0);
|
|
ExpectIntEQ(wc_AesInit(&sw, NULL, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_AesInit(&hwd, NULL, TEST_ASYNC_HSM_DEVID), 0);
|
|
ExpectIntEQ(wc_AesSetKey(&hw, test_async_key32(), 32, test_async_iv16(),
|
|
AES_ENCRYPTION), 0);
|
|
ExpectIntEQ(wc_AesSetKey(&sw, test_async_key32(), 32, test_async_iv16(),
|
|
AES_ENCRYPTION), 0);
|
|
ExpectIntEQ(wc_AesSetKey(&hwd, test_async_key32(), 32, test_async_iv16(),
|
|
AES_DECRYPTION), 0);
|
|
|
|
ExpectIntEQ(wc_AesCbcEncrypt(&sw, ref, test_async_plain32(), 32), 0);
|
|
|
|
for (pend = 0; pend <= TEST_ASYNC_MAX_PEND; pend++) {
|
|
test_hsm_reset(&hsm, pend);
|
|
/* CBC chains, so re-set the IV before each encrypt */
|
|
ExpectIntEQ(wc_AesSetIV(&hw, test_async_iv16()), 0);
|
|
ExpectIntEQ(test_async_drive_poll(&hw.asyncDev,
|
|
wc_AesCbcEncrypt(&hw, out, test_async_plain32(), 32)), 0);
|
|
ExpectIntEQ(hsm.submits, 1);
|
|
ExpectIntEQ(hsm.polls, pend + 1);
|
|
ExpectBufEQ(out, ref, 32);
|
|
}
|
|
|
|
/* decrypt side: poll-complete recovers the plaintext from ref */
|
|
hsm.offloadDec = 1;
|
|
for (pend = 0; pend <= TEST_ASYNC_MAX_PEND; pend++) {
|
|
test_hsm_reset(&hsm, pend);
|
|
ExpectIntEQ(wc_AesSetIV(&hwd, test_async_iv16()), 0);
|
|
ExpectIntEQ(test_async_drive_poll(&hwd.asyncDev,
|
|
wc_AesCbcDecrypt(&hwd, out, ref, 32)), 0);
|
|
ExpectIntEQ(hsm.submits, 1);
|
|
ExpectIntEQ(hsm.polls, pend + 1);
|
|
ExpectBufEQ(out, test_async_plain32(), 32);
|
|
}
|
|
|
|
wc_AesFree(&hw);
|
|
wc_AesFree(&sw);
|
|
wc_AesFree(&hwd);
|
|
if (registered)
|
|
wc_CryptoCb_UnRegisterDevice(TEST_ASYNC_HSM_DEVID);
|
|
#endif
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*
|
|
* AES-CCM record cipher: pends, poll-completes, matches software.
|
|
*/
|
|
int test_wc_CryptoCb_AsyncPollAesCcm(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(WOLFSSL_ASYNC_CRYPT) && defined(WOLF_CRYPTO_CB) && \
|
|
defined(WOLF_CRYPTO_CB_ASYNC_POLL) && defined(HAVE_AESCCM)
|
|
TestHsm hsm;
|
|
Aes hw, sw;
|
|
byte out[32], ref[32], tag[16], rtag[16];
|
|
int registered = 0;
|
|
int pend;
|
|
|
|
XMEMSET(&hsm, 0, sizeof(hsm));
|
|
XMEMSET(&hw, 0, sizeof(hw));
|
|
XMEMSET(&sw, 0, sizeof(sw));
|
|
|
|
ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_ASYNC_HSM_DEVID, test_hsm_cb,
|
|
&hsm), 0);
|
|
if (EXPECT_SUCCESS())
|
|
registered = 1;
|
|
|
|
ExpectIntEQ(wc_AesInit(&hw, NULL, TEST_ASYNC_HSM_DEVID), 0);
|
|
ExpectIntEQ(wc_AesInit(&sw, NULL, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_AesCcmSetKey(&hw, test_async_key32(), 16), 0);
|
|
ExpectIntEQ(wc_AesCcmSetKey(&sw, test_async_key32(), 16), 0);
|
|
|
|
ExpectIntEQ(wc_AesCcmEncrypt(&sw, ref, test_async_plain32(), 32,
|
|
test_async_iv12(), 12, rtag, 16, test_async_aad12(), 12), 0);
|
|
|
|
for (pend = 0; pend <= TEST_ASYNC_MAX_PEND; pend++) {
|
|
test_hsm_reset(&hsm, pend);
|
|
ExpectIntEQ(test_async_drive_poll(&hw.asyncDev,
|
|
wc_AesCcmEncrypt(&hw, out, test_async_plain32(), 32,
|
|
test_async_iv12(), 12, tag, 16, test_async_aad12(), 12)), 0);
|
|
ExpectIntEQ(hsm.submits, 1);
|
|
ExpectIntEQ(hsm.polls, pend + 1);
|
|
ExpectBufEQ(out, ref, 32);
|
|
ExpectBufEQ(tag, rtag, 16);
|
|
}
|
|
|
|
/* decrypt side: poll-complete recovers the plaintext (same CCM key) */
|
|
hsm.offloadDec = 1;
|
|
for (pend = 0; pend <= TEST_ASYNC_MAX_PEND; pend++) {
|
|
test_hsm_reset(&hsm, pend);
|
|
ExpectIntEQ(test_async_drive_poll(&hw.asyncDev,
|
|
wc_AesCcmDecrypt(&hw, out, ref, 32,
|
|
test_async_iv12(), 12, rtag, 16, test_async_aad12(), 12)), 0);
|
|
ExpectIntEQ(hsm.submits, 1);
|
|
ExpectIntEQ(hsm.polls, pend + 1);
|
|
ExpectBufEQ(out, test_async_plain32(), 32);
|
|
}
|
|
|
|
wc_AesFree(&hw);
|
|
wc_AesFree(&sw);
|
|
if (registered)
|
|
wc_CryptoCb_UnRegisterDevice(TEST_ASYNC_HSM_DEVID);
|
|
#endif
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*
|
|
* 3DES-CBC record cipher: pends, poll-completes, matches software. Covers the
|
|
* WOLFSSL_ASYNC_MARKER_3DES routing (not just AES).
|
|
*/
|
|
int test_wc_CryptoCb_AsyncPollDes3(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(WOLFSSL_ASYNC_CRYPT) && defined(WOLF_CRYPTO_CB) && \
|
|
defined(WOLF_CRYPTO_CB_ASYNC_POLL) && !defined(NO_DES3)
|
|
TestHsm hsm;
|
|
Des3 hw, sw, hwd;
|
|
byte out[32], ref[32];
|
|
static const byte key24[24] = {
|
|
0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,
|
|
0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0x01,
|
|
0x45,0x67,0x89,0xab,0xcd,0xef,0x01,0x23
|
|
};
|
|
static const byte iv8[8] = {
|
|
0x12,0x34,0x56,0x78,0x90,0xab,0xcd,0xef
|
|
};
|
|
int registered = 0;
|
|
int pend;
|
|
|
|
XMEMSET(&hsm, 0, sizeof(hsm));
|
|
XMEMSET(&hw, 0, sizeof(hw));
|
|
XMEMSET(&sw, 0, sizeof(sw));
|
|
XMEMSET(&hwd, 0, sizeof(hwd));
|
|
|
|
ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_ASYNC_HSM_DEVID, test_hsm_cb,
|
|
&hsm), 0);
|
|
if (EXPECT_SUCCESS())
|
|
registered = 1;
|
|
|
|
ExpectIntEQ(wc_Des3Init(&hw, NULL, TEST_ASYNC_HSM_DEVID), 0);
|
|
ExpectIntEQ(wc_Des3Init(&sw, NULL, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_Des3Init(&hwd, NULL, TEST_ASYNC_HSM_DEVID), 0);
|
|
ExpectIntEQ(wc_Des3_SetKey(&hw, key24, iv8, DES_ENCRYPTION), 0);
|
|
ExpectIntEQ(wc_Des3_SetKey(&sw, key24, iv8, DES_ENCRYPTION), 0);
|
|
ExpectIntEQ(wc_Des3_SetKey(&hwd, key24, iv8, DES_DECRYPTION), 0);
|
|
|
|
ExpectIntEQ(wc_Des3_CbcEncrypt(&sw, ref, test_async_plain32(), 32), 0);
|
|
|
|
for (pend = 0; pend <= TEST_ASYNC_MAX_PEND; pend++) {
|
|
test_hsm_reset(&hsm, pend);
|
|
/* CBC chains, so re-set the IV before each encrypt */
|
|
ExpectIntEQ(wc_Des3_SetIV(&hw, iv8), 0);
|
|
ExpectIntEQ(test_async_drive_poll(&hw.asyncDev,
|
|
wc_Des3_CbcEncrypt(&hw, out, test_async_plain32(), 32)), 0);
|
|
ExpectIntEQ(hsm.submits, 1);
|
|
ExpectIntEQ(hsm.polls, pend + 1);
|
|
ExpectBufEQ(out, ref, 32);
|
|
}
|
|
|
|
/* decrypt side: poll-complete recovers the plaintext from ref */
|
|
hsm.offloadDec = 1;
|
|
for (pend = 0; pend <= TEST_ASYNC_MAX_PEND; pend++) {
|
|
test_hsm_reset(&hsm, pend);
|
|
ExpectIntEQ(wc_Des3_SetIV(&hwd, iv8), 0);
|
|
ExpectIntEQ(test_async_drive_poll(&hwd.asyncDev,
|
|
wc_Des3_CbcDecrypt(&hwd, out, ref, 32)), 0);
|
|
ExpectIntEQ(hsm.submits, 1);
|
|
ExpectIntEQ(hsm.polls, pend + 1);
|
|
ExpectBufEQ(out, test_async_plain32(), 32);
|
|
}
|
|
|
|
wc_Des3Free(&hw);
|
|
wc_Des3Free(&sw);
|
|
wc_Des3Free(&hwd);
|
|
if (registered)
|
|
wc_CryptoCb_UnRegisterDevice(TEST_ASYNC_HSM_DEVID);
|
|
#endif
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*
|
|
* A cipher with a crypto callback dispatch that the HSM declines (AES-CTR):
|
|
* the callback IS entered (it has a dispatch) but returns unavailable, so the
|
|
* op falls back to software with no pend and no poll. Contrast with the
|
|
* unimplemented ciphers below, where the callback is never entered.
|
|
*/
|
|
int test_wc_CryptoCb_AsyncPollUnsupported(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(WOLFSSL_ASYNC_CRYPT) && defined(WOLF_CRYPTO_CB) && \
|
|
defined(WOLF_CRYPTO_CB_ASYNC_POLL) && defined(WOLFSSL_AES_COUNTER)
|
|
TestHsm hsm;
|
|
Aes hw, sw;
|
|
byte out[32], ref[32];
|
|
int registered = 0;
|
|
|
|
XMEMSET(&hsm, 0, sizeof(hsm));
|
|
XMEMSET(&hw, 0, sizeof(hw));
|
|
XMEMSET(&sw, 0, sizeof(sw));
|
|
|
|
ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_ASYNC_HSM_DEVID, test_hsm_cb,
|
|
&hsm), 0);
|
|
if (EXPECT_SUCCESS())
|
|
registered = 1;
|
|
|
|
ExpectIntEQ(wc_AesInit(&hw, NULL, TEST_ASYNC_HSM_DEVID), 0);
|
|
ExpectIntEQ(wc_AesInit(&sw, NULL, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_AesSetKey(&hw, test_async_key32(), 32, test_async_iv16(),
|
|
AES_ENCRYPTION), 0);
|
|
ExpectIntEQ(wc_AesSetKey(&sw, test_async_key32(), 32, test_async_iv16(),
|
|
AES_ENCRYPTION), 0);
|
|
|
|
/* declined by the HSM -> software fallback, completes synchronously */
|
|
ExpectIntEQ(wc_AesCtrEncrypt(&hw, out, test_async_plain32(), 32), 0);
|
|
ExpectIntGE(hsm.invocations, 1); /* dispatch exists: callback was entered */
|
|
ExpectIntEQ(hsm.submits, 0); /* but declined: not offloaded */
|
|
ExpectIntEQ(hsm.polls, 0);
|
|
|
|
ExpectIntEQ(wc_AesCtrEncrypt(&sw, ref, test_async_plain32(), 32), 0);
|
|
ExpectBufEQ(out, ref, 32);
|
|
|
|
wc_AesFree(&hw);
|
|
wc_AesFree(&sw);
|
|
if (registered)
|
|
wc_CryptoCb_UnRegisterDevice(TEST_ASYNC_HSM_DEVID);
|
|
#endif
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*
|
|
* Negative test: ChaCha20-Poly1305 is named in the crypto callback cipher type
|
|
* table (WC_CIPHER_CHACHA) but has no dispatch, so it is NOT offloadable. With
|
|
* a device registered, a ChaCha20-Poly1305 encrypt must never enter the
|
|
* callback and must run entirely in software. If crypto callback ChaCha
|
|
* support is ever added, this test will fail and must be updated.
|
|
*/
|
|
int test_wc_CryptoCb_AsyncPollChachaUnimpl(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(WOLFSSL_ASYNC_CRYPT) && defined(WOLF_CRYPTO_CB) && \
|
|
defined(WOLF_CRYPTO_CB_ASYNC_POLL) && defined(HAVE_CHACHA) && \
|
|
defined(HAVE_POLY1305)
|
|
TestHsm hsm;
|
|
byte out[32], tag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE];
|
|
static const byte key[CHACHA20_POLY1305_AEAD_KEYSIZE] = {
|
|
0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,
|
|
0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,
|
|
0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,
|
|
0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f
|
|
};
|
|
static const byte iv[CHACHA20_POLY1305_AEAD_IV_SIZE] = {
|
|
0x07,0x00,0x00,0x00,0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47
|
|
};
|
|
int registered = 0;
|
|
|
|
XMEMSET(&hsm, 0, sizeof(hsm));
|
|
|
|
ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_ASYNC_HSM_DEVID, test_hsm_cb,
|
|
&hsm), 0);
|
|
if (EXPECT_SUCCESS())
|
|
registered = 1;
|
|
|
|
/* no crypto callback dispatch for ChaCha: runs in software, cb untouched */
|
|
ExpectIntEQ(wc_ChaCha20Poly1305_Encrypt(key, iv,
|
|
test_async_aad12(), 12, test_async_plain32(), 32, out, tag), 0);
|
|
ExpectIntEQ(hsm.invocations, 0);
|
|
ExpectIntEQ(hsm.submits, 0);
|
|
ExpectIntEQ(hsm.polls, 0);
|
|
|
|
if (registered)
|
|
wc_CryptoCb_UnRegisterDevice(TEST_ASYNC_HSM_DEVID);
|
|
#endif
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*
|
|
* Negative test: single DES is named in the crypto callback cipher type table
|
|
* (WC_CIPHER_DES) but has no dispatch, so a DES-CBC encrypt with a device
|
|
* registered must never enter the callback and run entirely in software.
|
|
*/
|
|
int test_wc_CryptoCb_AsyncPollDesUnimpl(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(WOLFSSL_ASYNC_CRYPT) && defined(WOLF_CRYPTO_CB) && \
|
|
defined(WOLF_CRYPTO_CB_ASYNC_POLL) && !defined(NO_DES3)
|
|
TestHsm hsm;
|
|
Des des;
|
|
byte out[32];
|
|
static const byte key8[8] = {
|
|
0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef
|
|
};
|
|
static const byte iv8[8] = {
|
|
0x12,0x34,0x56,0x78,0x90,0xab,0xcd,0xef
|
|
};
|
|
int registered = 0;
|
|
|
|
XMEMSET(&hsm, 0, sizeof(hsm));
|
|
XMEMSET(&des, 0, sizeof(des));
|
|
|
|
ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_ASYNC_HSM_DEVID, test_hsm_cb,
|
|
&hsm), 0);
|
|
if (EXPECT_SUCCESS())
|
|
registered = 1;
|
|
|
|
ExpectIntEQ(wc_Des_SetKey(&des, key8, iv8, DES_ENCRYPTION), 0);
|
|
/* no crypto callback dispatch for single DES: cb untouched */
|
|
ExpectIntEQ(wc_Des_CbcEncrypt(&des, out, test_async_plain32(), 32), 0);
|
|
ExpectIntEQ(hsm.invocations, 0);
|
|
ExpectIntEQ(hsm.submits, 0);
|
|
ExpectIntEQ(hsm.polls, 0);
|
|
|
|
if (registered)
|
|
wc_CryptoCb_UnRegisterDevice(TEST_ASYNC_HSM_DEVID);
|
|
#endif
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*
|
|
* Full-stack TLS 1.3 tests. One generic harness drives an in-memory
|
|
* client/server handshake and an application-data echo through the crypto
|
|
* callback record cipher; only the callback implementation and the pinned
|
|
* ciphersuite are swapped per test.
|
|
*/
|
|
#if defined(WOLFSSL_ASYNC_CRYPT) && defined(WOLF_CRYPTO_CB) && \
|
|
defined(WOLF_CRYPTO_CB_ASYNC_POLL) && defined(WOLFSSL_TLS13) && \
|
|
defined(HAVE_AESGCM) && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
|
|
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
|
|
|
|
#define TEST_ASYNC_TLS
|
|
|
|
/* A device that offloads the AES-GCM record cipher but does NOT implement the
|
|
* poll completion (returns unavailable for WC_ALGO_TYPE_ASYNC_POLL). The op
|
|
* pends and can never finish, so the record layer must fail rather than ship
|
|
* an unencrypted record. */
|
|
static int test_hsm_nopoll_cb(int devId, wc_CryptoInfo* info, void* ctx)
|
|
{
|
|
TestHsm* hsm = (TestHsm*)ctx;
|
|
(void)devId;
|
|
|
|
if (info == NULL)
|
|
return WC_NO_ERR_TRACE(BAD_FUNC_ARG);
|
|
|
|
hsm->invocations++;
|
|
|
|
if (info->algo_type == WC_ALGO_TYPE_CIPHER && info->cipher.enc == 1 &&
|
|
info->cipher.type == WC_CIPHER_AES_GCM) {
|
|
hsm->submits++;
|
|
return WC_PENDING_E; /* pended, but no poll support */
|
|
}
|
|
if (info->algo_type == WC_ALGO_TYPE_ASYNC_POLL) {
|
|
hsm->polls++;
|
|
return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
|
|
}
|
|
return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
|
|
}
|
|
|
|
/* Drive one wolfSSL_write/read through, servicing async pends. Returns 1 if
|
|
* the message round-trips, 0 on failure. */
|
|
static int test_async_tls_echo(WOLFSSL* ssl_c, WOLFSSL* ssl_s)
|
|
{
|
|
const char msg[] = "hello async record layer";
|
|
byte buf[64];
|
|
int ret, err, i;
|
|
|
|
for (i = 0, ret = -1; i < 40; i++) {
|
|
ret = wolfSSL_write(ssl_c, msg, (int)sizeof(msg));
|
|
if (ret == (int)sizeof(msg))
|
|
break;
|
|
err = wolfSSL_get_error(ssl_c, ret);
|
|
if (err == WC_NO_ERR_TRACE(WC_PENDING_E))
|
|
(void)wolfSSL_AsyncPoll(ssl_c, WOLF_POLL_FLAG_CHECK_HW);
|
|
else if (err != WOLFSSL_ERROR_WANT_READ &&
|
|
err != WOLFSSL_ERROR_WANT_WRITE)
|
|
return 0;
|
|
}
|
|
if (ret != (int)sizeof(msg))
|
|
return 0;
|
|
|
|
for (i = 0, ret = -1; i < 40; i++) {
|
|
ret = wolfSSL_read(ssl_s, buf, (int)sizeof(buf));
|
|
if (ret > 0)
|
|
break;
|
|
err = wolfSSL_get_error(ssl_s, ret);
|
|
if (err == WC_NO_ERR_TRACE(WC_PENDING_E))
|
|
(void)wolfSSL_AsyncPoll(ssl_s, WOLF_POLL_FLAG_CHECK_HW);
|
|
else if (err != WOLFSSL_ERROR_WANT_READ &&
|
|
err != WOLFSSL_ERROR_WANT_WRITE)
|
|
return 0;
|
|
}
|
|
return (ret == (int)sizeof(msg) && XMEMCMP(buf, msg, sizeof(msg)) == 0);
|
|
}
|
|
|
|
typedef struct TlsAsyncResult {
|
|
int handshake; /* 0 = handshake completed */
|
|
int dataOk; /* 1 = echo round-tripped */
|
|
int cipherSubmits; /* record ciphers offloaded to the device */
|
|
} TlsAsyncResult;
|
|
|
|
/* Generic harness: register the given callback, run a TLS 1.3 handshake and
|
|
* echo over memio with the given ciphersuite pinned, and report the outcome
|
|
* plus how many record ciphers were offloaded. */
|
|
static void test_async_tls_run(CryptoDevCallbackFunc cb, const char* cipherList,
|
|
int pendPolls, TlsAsyncResult* res)
|
|
{
|
|
WOLFSSL_CTX* ctx_c = NULL;
|
|
WOLFSSL_CTX* ctx_s = NULL;
|
|
WOLFSSL* ssl_c = NULL;
|
|
WOLFSSL* ssl_s = NULL;
|
|
struct test_memio_ctx test_ctx;
|
|
TestHsm hsm;
|
|
int registered = 0;
|
|
|
|
res->handshake = -1;
|
|
res->dataOk = 0;
|
|
res->cipherSubmits = 0;
|
|
|
|
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
|
|
XMEMSET(&hsm, 0, sizeof(hsm));
|
|
hsm.pendPolls = pendPolls; /* polls each record cipher stays in-flight */
|
|
|
|
if (wc_CryptoCb_RegisterDevice(TEST_ASYNC_HSM_DEVID, cb, &hsm) != 0)
|
|
return;
|
|
registered = 1;
|
|
|
|
if (test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
|
|
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method) != 0)
|
|
goto cleanup;
|
|
|
|
(void)wolfSSL_CTX_SetDevId(ctx_c, TEST_ASYNC_HSM_DEVID);
|
|
(void)wolfSSL_CTX_SetDevId(ctx_s, TEST_ASYNC_HSM_DEVID);
|
|
(void)wolfSSL_SetDevId(ssl_c, TEST_ASYNC_HSM_DEVID);
|
|
(void)wolfSSL_SetDevId(ssl_s, TEST_ASYNC_HSM_DEVID);
|
|
|
|
if (cipherList != NULL) {
|
|
if (wolfSSL_set_cipher_list(ssl_c, cipherList) != WOLFSSL_SUCCESS ||
|
|
wolfSSL_set_cipher_list(ssl_s, cipherList) != WOLFSSL_SUCCESS)
|
|
goto cleanup;
|
|
}
|
|
|
|
res->handshake = test_memio_do_handshake(ssl_c, ssl_s, 10, NULL);
|
|
if (res->handshake == 0)
|
|
res->dataOk = test_async_tls_echo(ssl_c, ssl_s);
|
|
res->cipherSubmits = hsm.submits;
|
|
|
|
cleanup:
|
|
if (ssl_c != NULL)
|
|
wolfSSL_free(ssl_c);
|
|
if (ssl_s != NULL)
|
|
wolfSSL_free(ssl_s);
|
|
if (ctx_c != NULL)
|
|
wolfSSL_CTX_free(ctx_c);
|
|
if (ctx_s != NULL)
|
|
wolfSSL_CTX_free(ctx_s);
|
|
if (registered)
|
|
wc_CryptoCb_UnRegisterDevice(TEST_ASYNC_HSM_DEVID);
|
|
}
|
|
|
|
#endif /* TEST_ASYNC_TLS */
|
|
|
|
/*
|
|
* Full TLS 1.3 with AES-GCM offloaded to a poll-completing device: the
|
|
* handshake and an application-data echo both succeed, and the record cipher
|
|
* is offloaded (poll completion drives the encrypted records).
|
|
*/
|
|
int test_wc_CryptoCb_AsyncPollTlsAesGcm(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#ifdef TEST_ASYNC_TLS
|
|
TlsAsyncResult res;
|
|
int pend;
|
|
|
|
/* pend depth 0..TEST_ASYNC_MAX_PEND: each record cipher stays in-flight for
|
|
* that many extra polls before completing. */
|
|
for (pend = 0; pend <= TEST_ASYNC_MAX_PEND; pend++) {
|
|
test_async_tls_run(test_hsm_cb,
|
|
"TLS13-AES128-GCM-SHA256:TLS13-AES256-GCM-SHA384", pend, &res);
|
|
|
|
ExpectIntEQ(res.handshake, 0);
|
|
ExpectIntEQ(res.dataOk, 1);
|
|
ExpectIntGT(res.cipherSubmits, 0);
|
|
}
|
|
#endif
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*
|
|
* Full TLS 1.3 with ChaCha20-Poly1305: ChaCha has no crypto callback dispatch,
|
|
* so the record cipher runs in software. The handshake and echo still succeed
|
|
* and the device is never asked to offload a record cipher.
|
|
*/
|
|
int test_wc_CryptoCb_AsyncPollTlsChachaNotOffloaded(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(TEST_ASYNC_TLS) && defined(HAVE_CHACHA) && defined(HAVE_POLY1305)
|
|
TlsAsyncResult res;
|
|
|
|
test_async_tls_run(test_hsm_cb, "TLS13-CHACHA20-POLY1305-SHA256", 1, &res);
|
|
|
|
ExpectIntEQ(res.handshake, 0);
|
|
ExpectIntEQ(res.dataOk, 1);
|
|
ExpectIntEQ(res.cipherSubmits, 0); /* ChaCha not dispatched to the device */
|
|
#endif
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*
|
|
* Full TLS 1.3 with AES-GCM offloaded to a device that pends the record cipher
|
|
* but does NOT implement poll completion: the handshake must fail rather than
|
|
* transmit an unencrypted record.
|
|
*/
|
|
int test_wc_CryptoCb_AsyncPollTlsNoPollFails(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#ifdef TEST_ASYNC_TLS
|
|
TlsAsyncResult res;
|
|
|
|
test_async_tls_run(test_hsm_nopoll_cb,
|
|
"TLS13-AES128-GCM-SHA256:TLS13-AES256-GCM-SHA384", 1, &res);
|
|
|
|
ExpectIntNE(res.handshake, 0); /* expected failure */
|
|
ExpectIntEQ(res.dataOk, 0);
|
|
ExpectIntGT(res.cipherSubmits, 0); /* it did pend a record cipher */
|
|
#endif
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*
|
|
* Full TLS 1.3 with AES-GCM offloaded on BOTH the encrypt and decrypt record
|
|
* paths. Each peer uses its own poll-completing device, so a client decrypt and
|
|
* a server encrypt never share one job slot. The handshake and echo succeed and
|
|
* both peers offload at least one decrypt through poll completion, confirming
|
|
* the poll model drives the receive (decrypt) side, not just send.
|
|
*/
|
|
int test_wc_CryptoCb_AsyncPollTlsBothDirections(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#ifdef TEST_ASYNC_TLS
|
|
WOLFSSL_CTX* ctx_c = NULL;
|
|
WOLFSSL_CTX* ctx_s = NULL;
|
|
WOLFSSL* ssl_c = NULL;
|
|
WOLFSSL* ssl_s = NULL;
|
|
struct test_memio_ctx test_ctx;
|
|
TestHsm hsm_c, hsm_s;
|
|
int reg_c = 0, reg_s = 0;
|
|
int handshake = -1, dataOk = 0;
|
|
const char* ciphers = "TLS13-AES128-GCM-SHA256:TLS13-AES256-GCM-SHA384";
|
|
|
|
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
|
|
XMEMSET(&hsm_c, 0, sizeof(hsm_c));
|
|
XMEMSET(&hsm_s, 0, sizeof(hsm_s));
|
|
hsm_c.pendPolls = 1; hsm_c.offloadDec = 1;
|
|
hsm_s.pendPolls = 1; hsm_s.offloadDec = 1;
|
|
|
|
ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_ASYNC_HSM_DEVID, test_hsm_cb,
|
|
&hsm_c), 0);
|
|
if (EXPECT_SUCCESS())
|
|
reg_c = 1;
|
|
ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_ASYNC_HSM_DEVID2, test_hsm_cb,
|
|
&hsm_s), 0);
|
|
if (EXPECT_SUCCESS())
|
|
reg_s = 1;
|
|
|
|
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
|
|
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
|
|
|
|
if (ssl_c != NULL && ssl_s != NULL) {
|
|
(void)wolfSSL_CTX_SetDevId(ctx_c, TEST_ASYNC_HSM_DEVID);
|
|
(void)wolfSSL_CTX_SetDevId(ctx_s, TEST_ASYNC_HSM_DEVID2);
|
|
(void)wolfSSL_SetDevId(ssl_c, TEST_ASYNC_HSM_DEVID);
|
|
(void)wolfSSL_SetDevId(ssl_s, TEST_ASYNC_HSM_DEVID2);
|
|
|
|
ExpectIntEQ(wolfSSL_set_cipher_list(ssl_c, ciphers), WOLFSSL_SUCCESS);
|
|
ExpectIntEQ(wolfSSL_set_cipher_list(ssl_s, ciphers), WOLFSSL_SUCCESS);
|
|
|
|
/* Both directions offloaded on both peers means many more poll cycles
|
|
* than the encrypt-only tests, so allow more handshake rounds. */
|
|
handshake = test_memio_do_handshake(ssl_c, ssl_s, 64, NULL);
|
|
ExpectIntEQ(handshake, 0);
|
|
if (handshake == 0)
|
|
dataOk = test_async_tls_echo(ssl_c, ssl_s);
|
|
ExpectIntEQ(dataOk, 1);
|
|
|
|
/* both peers offloaded record ciphers, including >=1 decrypt each */
|
|
ExpectIntGT(hsm_c.submits, 0);
|
|
ExpectIntGT(hsm_s.submits, 0);
|
|
ExpectIntGT(hsm_c.decSubmits, 0);
|
|
ExpectIntGT(hsm_s.decSubmits, 0);
|
|
}
|
|
|
|
wolfSSL_free(ssl_c);
|
|
wolfSSL_free(ssl_s);
|
|
wolfSSL_CTX_free(ctx_c);
|
|
wolfSSL_CTX_free(ctx_s);
|
|
if (reg_c)
|
|
wc_CryptoCb_UnRegisterDevice(TEST_ASYNC_HSM_DEVID);
|
|
if (reg_s)
|
|
wc_CryptoCb_UnRegisterDevice(TEST_ASYNC_HSM_DEVID2);
|
|
#endif
|
|
return EXPECT_RESULT();
|
|
}
|