Skip to content

Commit d00a06b

Browse files
committed
Address skoll: fix type-confused ephemeral-key free on KEX switch, cppcheck redundant assign, KEY_EXCHANGE/reconnect tests, rename PQC workflow
1 parent ed2c935 commit d00a06b

4 files changed

Lines changed: 102 additions & 14 deletions

File tree

.github/workflows/spdm-emu-pqc-test.yml

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
name: SPDM Emulator PQC (ML-DSA) Test
1+
name: SPDM Emulator PQC Test
22

3-
# Post-quantum interop, mirroring the classical SPDM Emulator Integration Test
4-
# matrix (ubuntu 22.04/24.04 x64 + 24.04 aarch64, each with static and dynamic
5-
# memory). The wc_MlDsaKey context API wolfSPDM verifies with lands
6-
# post-v5.9.1-stable, so this job pins wolfSSL master. libspdm's ML-DSA is only
7-
# in its OpenSSL backend (the mbedtls backend stubs it out), so spdm-emu is
8-
# built with CRYPTO=openssl.
3+
# Post-quantum interop (ML-DSA signing + ML-KEM key exchange, and a full-PQ
4+
# combination), mirroring the classical SPDM Emulator Integration Test matrix
5+
# (ubuntu 22.04/24.04 x64 + 24.04 aarch64, each with static and dynamic memory).
6+
# The wc_MlDsaKey context API wolfSPDM verifies with lands post-v5.9.1-stable,
7+
# so this job pins wolfSSL master. libspdm's ML-DSA/ML-KEM are only in its
8+
# OpenSSL backend (the mbedtls backend stubs them out), so spdm-emu is built
9+
# with CRYPTO=openssl.
910

1011
on:
1112
push:
@@ -85,7 +86,7 @@ jobs:
8586
make -j"$(nproc)"
8687
make install
8788
88-
- name: Run unit tests (includes ML-DSA verify)
89+
- name: Run unit tests (includes ML-DSA verify + ML-KEM decap)
8990
run: make check
9091
env:
9192
LD_LIBRARY_PATH: ${{ github.workspace }}/.libs:${{ github.workspace }}/src/.libs:${{ env.HOME }}/wolfssl-install/lib

src/spdm_context.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,9 @@ int wolfSPDM_SetKeyExchangePref(WOLFSPDM_CTX* ctx, int advDhe, word16 kemMask)
300300
if (ctx == NULL) {
301301
return WOLFSPDM_E_INVALID_ARG;
302302
}
303+
if (advDhe == 0 && kemMask == 0) {
304+
return WOLFSPDM_E_INVALID_ARG; /* must advertise at least one method */
305+
}
303306
#ifndef WOLFSPDM_HAVE_MLKEM
304307
if (kemMask != 0) {
305308
return WOLFSPDM_E_INVALID_ARG; /* ML-KEM not built in */

src/spdm_msg.c

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,14 @@ int wolfSPDM_BuildNegotiateAlgorithms(WOLFSPDM_CTX* ctx, byte* buf, word32* bufS
7979
/* Fixed header (32 bytes) + up to 5 AlgStructs (4 bytes each) = 52. */
8080
SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 52);
8181

82-
advDhe = (ctx->kexAdvDhe != 0);
8382
#ifdef WOLFSPDM_HAVE_MLKEM
83+
advDhe = (ctx->kexAdvDhe != 0);
8484
advKem = (ctx->spdmVersion >= SPDM_VERSION_14 && ctx->kexAdvKem != 0);
8585
if (!advDhe && !advKem) {
8686
advDhe = 1; /* never advertise zero key-exchange methods */
8787
}
8888
#else
89-
advDhe = 1;
89+
advDhe = 1; /* DHE is the only key-exchange method without ML-KEM */
9090
#endif
9191

9292
XMEMSET(buf, 0, 52);
@@ -704,6 +704,16 @@ int wolfSPDM_ParseAlgorithms(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz)
704704
ctx->flags.hasResponderPubKey = 0;
705705
}
706706

707+
/* Same hazard for the ephemeral key union: a prior handshake may have left a
708+
* key live whose union member is named by the OLD ctx->kexType. Free it now,
709+
* before kexType is reassigned below, so the free dispatches on the matching
710+
* member (otherwise a reconnect that switches DHE<->ML-KEM would type-confuse
711+
* the free in wolfSPDM_FreeEphemeralKey). */
712+
if (ctx->flags.ephemeralKeyInit) {
713+
wolfSPDM_FreeEphemeralKey(ctx);
714+
ctx->flags.ephemeralKeyInit = 0;
715+
}
716+
707717
/* DSP0274 1.4 Table 20: PqcAsymSel (offset 20). Present from 1.4; earlier
708718
* versions leave these bytes reserved-zero. The spec caps the combined
709719
* bit count of BaseAsymSel and PqcAsymSel at one, so exactly one of the
@@ -1000,10 +1010,6 @@ int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufS
10001010
ctx->rspSessionId = SPDM_Get16LE(&buf[4]);
10011011
ctx->sessionId = (word32)ctx->reqSessionId | ((word32)ctx->rspSessionId << 16);
10021012

1003-
/* Extract responder's ephemeral public key (offset 40 = 4+2+1+1+32) */
1004-
XMEMCPY(peerPubKeyX, &buf[40], WOLFSPDM_ECC_KEY_SIZE);
1005-
XMEMCPY(peerPubKeyY, &buf[88], WOLFSPDM_ECC_KEY_SIZE);
1006-
10071013
signature = buf + sigOffset;
10081014
rspVerifyData = buf + sigOffset + sigSize;
10091015

@@ -1052,6 +1058,9 @@ int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufS
10521058
else
10531059
#endif
10541060
if (ctx->kexType == WOLFSPDM_KEX_ECDHE) {
1061+
/* ExchangeData is the responder's ephemeral point X||Y at offset 40. */
1062+
XMEMCPY(peerPubKeyX, &buf[40], WOLFSPDM_ECC_KEY_SIZE);
1063+
XMEMCPY(peerPubKeyY, &buf[88], WOLFSPDM_ECC_KEY_SIZE);
10551064
rc = wolfSPDM_ComputeSharedSecret(ctx, peerPubKeyX, peerPubKeyY);
10561065
}
10571066
if (rc != WOLFSPDM_SUCCESS) {

test/unit_test.c

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,79 @@ static int test_mlkem_decapsulate(void)
795795
TEST_CTX_FREE();
796796
TEST_PASS();
797797
}
798+
799+
/* Reconnect on a reused context switching key-exchange method must free the
800+
* prior ephemeral key while ctx->kexType still names its union member (no
801+
* type-confused free). Run under valgrind in CI to catch a mismatched free. */
802+
static int test_kex_reconnect_method_switch(void)
803+
{
804+
byte ek[WOLFSPDM_MLKEM768_EK_SIZE];
805+
byte rsp[72];
806+
word32 ekSz = sizeof(ek);
807+
word32 len;
808+
TEST_CTX_SETUP();
809+
810+
printf("test_kex_reconnect_method_switch...\n");
811+
ctx->spdmVersion = SPDM_VERSION_14;
812+
813+
/* Round 1 leaves a live ML-KEM ephemeral key. */
814+
ctx->kemAlgSel = SPDM_KEM_ALGO_ML_KEM_768;
815+
ASSERT_SUCCESS(wolfSPDM_GenerateMlKemKey(ctx, ek, &ekSz));
816+
ASSERT_EQ(ctx->flags.ephemeralKeyInit, 1, "ML-KEM key live");
817+
ASSERT_EQ(ctx->kexType, WOLFSPDM_KEX_MLKEM, "kexType MLKEM");
818+
819+
/* Reconnect negotiates ECDHE: ParseAlgorithms frees the live ML-KEM key
820+
* (still matching kexType) before flipping to ECDHE. */
821+
len = build_algorithms_14_kem(rsp, SPDM_DHE_ALGO_SECP384R1, 0);
822+
ASSERT_SUCCESS(wolfSPDM_ParseAlgorithms(ctx, rsp, len));
823+
ASSERT_EQ(ctx->kexType, WOLFSPDM_KEX_ECDHE, "switched to ECDHE");
824+
ASSERT_EQ(ctx->flags.ephemeralKeyInit, 0, "stale ML-KEM key freed");
825+
826+
/* And the reverse: a live ECDHE key, then a reconnect negotiating ML-KEM. */
827+
ASSERT_SUCCESS(wolfSPDM_GenerateEphemeralKey(ctx));
828+
ASSERT_EQ(ctx->flags.ephemeralKeyInit, 1, "ECDHE key live");
829+
len = build_algorithms_14_kem(rsp, 0, SPDM_KEM_ALGO_ML_KEM_768);
830+
ASSERT_SUCCESS(wolfSPDM_ParseAlgorithms(ctx, rsp, len));
831+
ASSERT_EQ(ctx->kexType, WOLFSPDM_KEX_MLKEM, "switched to ML-KEM");
832+
ASSERT_EQ(ctx->flags.ephemeralKeyInit, 0, "stale ECDHE key freed");
833+
834+
TEST_CTX_FREE();
835+
TEST_PASS();
836+
}
837+
838+
/* KEY_EXCHANGE with ML-KEM places the encapsulation key ek as ExchangeData at
839+
* offset 40; confirm it decodes as a valid ML-KEM-768 public key. */
840+
static int test_build_key_exchange_mlkem(void)
841+
{
842+
byte buf[WOLFSPDM_KEX_REQ_BUF];
843+
word32 bufSz = sizeof(buf);
844+
MlKemKey check;
845+
int checkInit = 0;
846+
TEST_CTX_SETUP();
847+
848+
printf("test_build_key_exchange_mlkem...\n");
849+
ctx->spdmVersion = SPDM_VERSION_14;
850+
ctx->kexType = WOLFSPDM_KEX_MLKEM;
851+
ctx->kemAlgSel = SPDM_KEM_ALGO_ML_KEM_768;
852+
853+
ASSERT_SUCCESS(wolfSPDM_BuildKeyExchange(ctx, buf, &bufSz));
854+
ASSERT_EQ(buf[1], SPDM_KEY_EXCHANGE, "KEY_EXCHANGE code");
855+
/* offset 40 = 4 hdr + 2 sessionId + 2 policy/rsvd + 32 random. */
856+
ASSERT_EQ(wc_MlKemKey_Init(&check, WC_ML_KEM_768, NULL, INVALID_DEVID), 0,
857+
"MlKemKey_Init");
858+
checkInit = 1;
859+
ASSERT_EQ(wc_MlKemKey_DecodePublicKey(&check, &buf[40],
860+
WOLFSPDM_MLKEM768_EK_SIZE), 0, "ek decodes at offset 40");
861+
/* 40 fixed + 1184 ek + 22 OpaqueData block. */
862+
ASSERT_EQ(bufSz, 40u + WOLFSPDM_MLKEM768_EK_SIZE + 22u,
863+
"ML-KEM KEY_EXCHANGE total size");
864+
865+
if (checkInit) {
866+
wc_MlKemKey_Free(&check);
867+
}
868+
TEST_CTX_FREE();
869+
TEST_PASS();
870+
}
798871
#endif /* WOLFSPDM_HAVE_MLKEM */
799872

800873
#ifdef WOLFSPDM_HAVE_CHUNK
@@ -2824,6 +2897,8 @@ int main(void)
28242897
test_negotiate_algorithms_kem_build();
28252898
test_parse_algorithms_kem_select();
28262899
test_mlkem_decapsulate();
2900+
test_kex_reconnect_method_switch();
2901+
test_build_key_exchange_mlkem();
28272902
#endif
28282903
#ifdef WOLFSPDM_HAVE_CHUNK
28292904
test_chunk_reassemble();

0 commit comments

Comments
 (0)