Skip to content

Commit d81fedb

Browse files
authored
Merge pull request #11366 from danielinux/fenrir-fixes-2026-09-03
TROPIC01: Keep the caller's IV in the AES-CBC callback, derive the GHASH subkey from device key
2 parents 3473701 + 821c86a commit d81fedb

4 files changed

Lines changed: 507 additions & 35 deletions

File tree

.github/workflows/tropic01-sim.yml

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ permissions:
2525
# Build the TROPIC01 software simulator (https://github.com/wolfSSL/simulators,
2626
# TROPIC01Sim/ subdirectory), build wolfSSL --with-tropic01 against libtropic
2727
# v0.1.0 + the simulator's TCP HAL, and run Tropic Square's wolfssl-test app
28-
# against the simulator TCP server.
28+
# against the simulator TCP server. Also runs the in-tree AES-GCM and
29+
# AES-CBC callback known-answer tests (tests/tropic01_gcm_kat.c and
30+
# tests/tropic01_cbc_kat.c), which verify the callback's GCM and CBC
31+
# key-swap paths against precomputed vectors.
2932
#
3033
# The simulator's own Dockerfile.wolfcrypt clones wolfSSL master and applies
3134
# one sed patch to it (s/ForceZero/wc_ForceZero/ in
@@ -100,3 +103,49 @@ jobs:
100103

101104
- name: Run wolfCrypt tests against simulator
102105
run: docker run --rm wolfssl-tropic01-sim:ci
106+
107+
# The upstream test app covers RNG / AES-CBC / Ed25519 only. This runs
108+
# both in-tree callback KATs: each drives Tropic01_CryptoCb the same
109+
# way TLS does (context created with WOLF_TROPIC01_DEVID and set with
110+
# a caller key) and checks the ciphertext (plus the auth tag for GCM)
111+
# against precomputed vectors for the device-resident key, so a GHASH
112+
# subkey left stale from the caller's key, or a CBC chain restarted
113+
# from the device IV, is caught.
114+
- name: Run TROPIC01 callback KATs
115+
run: |
116+
docker run --rm wolfssl-tropic01-sim:ci bash -c '
117+
set -eu
118+
# Compile the KATs with the exact CFLAGS the library was built
119+
# with (LIBWOLFSSL_GLOBAL_CFLAGS in .build_params), so the Aes
120+
# struct layout matches the installed libwolfssl.a. The -I and
121+
# -W flags are dropped and re-added explicitly below.
122+
FLAGS=$(sed -n "s/^#define LIBWOLFSSL_GLOBAL_CFLAGS \"\(.*\)\" LIBWOLFSSL_GLOBAL_EXTRA_CFLAGS$/\1/p" /app/wolfssl/.build_params | tr " " "\n" | grep -v -- "-include" | grep -v "build_params" | grep -v -- "-I" | grep -v -- "-pthread" | grep -v -- "-Werror" | grep -v -- "-W" | tr "\n" " ")
123+
gcc -Wall -Wextra -O2 ${FLAGS} \
124+
-I/app/libtropic/include -I/app/wolfssl \
125+
/app/wolfssl/tests/tropic01_gcm_kat.c \
126+
/app/libtropic/hal/port/unix/lt_port_unix_tcp.c \
127+
-L/usr/local/lib -L/app/libtropic/build \
128+
-L/app/libtropic/build/trezor_crypto \
129+
-lwolfssl -ltropic -ltrezor_crypto -lm \
130+
-o /tmp/tropic01_gcm_kat
131+
gcc -Wall -Wextra -O2 ${FLAGS} \
132+
-I/app/libtropic/include -I/app/wolfssl \
133+
/app/wolfssl/tests/tropic01_cbc_kat.c \
134+
/app/libtropic/hal/port/unix/lt_port_unix_tcp.c \
135+
-L/usr/local/lib -L/app/libtropic/build \
136+
-L/app/libtropic/build/trezor_crypto \
137+
-lwolfssl -ltropic -ltrezor_crypto -lm \
138+
-o /tmp/tropic01_cbc_kat
139+
TROPIC01_SIM_BIND=127.0.0.1 TROPIC01_SIM_PORT=28992 \
140+
TROPIC01_SIM_FRESH=1 /app/tcp_server &
141+
SIM_PID=$!
142+
trap "kill ${SIM_PID} 2>/dev/null || true" EXIT
143+
for i in $(seq 1 50); do
144+
if (echo > /dev/tcp/127.0.0.1/28992) 2>/dev/null; then
145+
break
146+
fi
147+
sleep 0.1
148+
done
149+
/tmp/tropic01_gcm_kat
150+
/tmp/tropic01_cbc_kat
151+
'

tests/tropic01_cbc_kat.c

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
/* tropic01_cbc_kat.c
2+
*
3+
* Copyright (C) 2006-2026 wolfSSL Inc.
4+
*
5+
* This file is part of wolfSSL.
6+
*
7+
* wolfSSL is free software; you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation; either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* wolfSSL is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program; if not, write to the Free Software
19+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
20+
*/
21+
22+
/* Known-answer test for the AES-CBC branch of the TROPIC01 crypto
23+
* callback (Tropic01_CryptoCb).
24+
*
25+
* The callback swaps the device-resident key into the caller's Aes
26+
* context and runs the CBC operation in software. The operation must
27+
* chain from the IV the caller installed on the context: wc_AesSetKey()
28+
* overwrites aes->reg, so the callback has to preserve the caller's
29+
* chaining state across the key swap. A callback that installs a
30+
* device-provisioned IV instead makes CBC deterministic - identical
31+
* plaintexts produce identical ciphertexts - and silently discards the
32+
* per-message IV the application chose.
33+
*
34+
* The KAT vectors are computed with AES-128-CBC under the TROPIC01Sim
35+
* default AES key (object-store R-memory slot 0, byte i is i*0x11 mod
36+
* 256; the callback passes the first keyLen bytes of the slot to the
37+
* software path) and the caller IV below, which is deliberately
38+
* different from the device IV fixture (R-memory slot 1, 00 01 02 ... 0f)
39+
* so a callback that restarts the chain from the device IV fails the
40+
* comparison.
41+
*
42+
* The test drives the callback the same way TLS does: the context is
43+
* created with WOLF_TROPIC01_DEVID and set with a caller (decoy) key
44+
* and the KAT IV. Only a callback that keeps the caller's IV produces
45+
* the KAT ciphertext.
46+
*
47+
* Build (against the installed libwolfssl and libtropic v0.1.0, see
48+
* .github/workflows/tropic01-sim.yml): the installed headers carry no
49+
* options.h, so the feature macros of the library build must be
50+
* repeated on the command line.
51+
* gcc -Wall -Wextra -O2 -DWOLFSSL_TROPIC01 -DWOLF_CRYPTO_CB \
52+
* -DHAVE_AES_CBC \
53+
* -I<libtropic>/include -I<prefix>/include \
54+
* tests/tropic01_cbc_kat.c \
55+
* <libtropic>/hal/port/unix/lt_port_unix_tcp.c \
56+
* -L<prefix>/lib -L<libtropic>/build \
57+
* -L<libtropic>/build/trezor_crypto \
58+
* -lwolfssl -ltropic -ltrezor_crypto -lm \
59+
* -o tropic01_cbc_kat
60+
*
61+
* Run with the TROPIC01Sim tcp_server listening (TROPIC01_SIM_HOST,
62+
* TROPIC01_SIM_PORT). Exits 0 on pass, 1 on fail.
63+
*/
64+
65+
#include <stdio.h>
66+
67+
#include <wolfssl/wolfcrypt/aes.h>
68+
#include <wolfssl/wolfcrypt/cryptocb.h>
69+
#include <wolfssl/wolfcrypt/error-crypt.h>
70+
#include <wolfssl/wolfcrypt/port/tropicsquare/tropic01.h>
71+
72+
#define KAT_PT_SZ 32
73+
74+
/* Caller (decoy) session key the test sets on the context, standing
75+
* in for the TLS key. The callback swaps in the device key. */
76+
static const byte kat_key_decoy[AES_128_KEY_SIZE] = {
77+
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a,
78+
0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a
79+
};
80+
81+
/* Per-message IV the caller installs on the context. */
82+
static const byte kat_iv[WC_AES_BLOCK_SIZE] = {
83+
0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09,
84+
0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01
85+
};
86+
87+
static const byte kat_pt[KAT_PT_SZ] = {
88+
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
89+
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
90+
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
91+
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f
92+
};
93+
94+
/* AES-128-CBC(kat_key_dev, kat_iv, kat_pt). */
95+
static const byte kat_ct[KAT_PT_SZ] = {
96+
0x0d, 0x97, 0xca, 0x41, 0x6c, 0x68, 0x39, 0x5f,
97+
0x37, 0x91, 0x91, 0x30, 0xe8, 0xd1, 0x35, 0x77,
98+
0x78, 0x6e, 0x86, 0x02, 0x31, 0x6a, 0x80, 0x0f,
99+
0x4e, 0x1a, 0x7d, 0xe9, 0x62, 0xe8, 0x1d, 0x28
100+
};
101+
102+
/* Default host pairing keys (libtropic sh0 sample), matching the
103+
* TROPIC01Sim object store defaults. */
104+
static const byte kat_sh0_priv[TROPIC01_PAIRING_KEY_SIZE] = {
105+
0xd0, 0x99, 0x92, 0xb1, 0xf1, 0x7a, 0xbc, 0x4d,
106+
0xb9, 0x37, 0x17, 0x68, 0xa2, 0x7d, 0xa0, 0x5b,
107+
0x18, 0xfa, 0xb8, 0x56, 0x13, 0xa7, 0x84, 0x2c,
108+
0xa6, 0x4c, 0x79, 0x10, 0xf2, 0x2e, 0x71, 0x6b
109+
};
110+
111+
static const byte kat_sh0_pub[TROPIC01_PAIRING_KEY_SIZE] = {
112+
0xe7, 0xf7, 0x35, 0xba, 0x19, 0xa3, 0x3f, 0xd6,
113+
0x73, 0x23, 0xab, 0x37, 0x26, 0x2d, 0xe5, 0x36,
114+
0x08, 0xca, 0x57, 0x85, 0x76, 0x53, 0x43, 0x52,
115+
0xe1, 0x8f, 0x64, 0xe6, 0x13, 0xd3, 0x8d, 0x54
116+
};
117+
118+
static int kat_check(const char* label, const byte* got,
119+
const byte* want, word32 sz)
120+
{
121+
if (XMEMCMP(got, want, sz) != 0) {
122+
printf("FAIL: %s mismatch\n", label);
123+
return 0;
124+
}
125+
printf("PASS: %s matches KAT\n", label);
126+
return 1;
127+
}
128+
129+
int main(void)
130+
{
131+
int ret = 0;
132+
int ok = 0;
133+
int inited = 0;
134+
Aes enc;
135+
Aes dec;
136+
byte ct[KAT_PT_SZ];
137+
byte out[KAT_PT_SZ];
138+
139+
printf("TROPIC01 CBC callback KAT\n");
140+
141+
ret = Tropic01_SetPairingKeys(PAIRING_KEY_SLOT_INDEX_0,
142+
kat_sh0_pub, kat_sh0_priv);
143+
if (ret != 0) {
144+
printf("FAIL: Tropic01_SetPairingKeys: %d\n", ret);
145+
goto cleanup;
146+
}
147+
148+
ret = wolfCrypt_Init();
149+
if (ret != 0) {
150+
printf("FAIL: wolfCrypt_Init: %d\n", ret);
151+
goto cleanup;
152+
}
153+
inited = 1;
154+
155+
ret = wc_CryptoCb_RegisterDevice(WOLF_TROPIC01_DEVID,
156+
Tropic01_CryptoCb, NULL);
157+
if (ret != 0) {
158+
printf("FAIL: wc_CryptoCb_RegisterDevice: %d\n", ret);
159+
goto cleanup;
160+
}
161+
162+
/* Encrypt KAT: the context is set with the decoy key and the KAT
163+
* IV. The callback must swap in the device key while chaining from
164+
* the caller's IV. */
165+
ret = wc_AesInit(&enc, NULL, WOLF_TROPIC01_DEVID);
166+
if (ret == 0) {
167+
ret = wc_AesSetKey(&enc, kat_key_decoy, AES_128_KEY_SIZE,
168+
kat_iv, AES_ENCRYPTION);
169+
}
170+
if (ret == 0) {
171+
ret = wc_AesCbcEncrypt(&enc, ct, kat_pt, KAT_PT_SZ);
172+
}
173+
if (ret == 0) {
174+
ok = kat_check("ciphertext", ct, kat_ct, KAT_PT_SZ);
175+
}
176+
else {
177+
printf("FAIL: encrypt KAT op: %d\n", ret);
178+
ok = 0;
179+
}
180+
wc_AesFree(&enc);
181+
182+
/* Decrypt KAT: the KAT ciphertext only decrypts to the plaintext
183+
* when the decrypt path chains from the caller's IV as well. */
184+
ret = wc_AesInit(&dec, NULL, WOLF_TROPIC01_DEVID);
185+
if (ret == 0) {
186+
ret = wc_AesSetKey(&dec, kat_key_decoy, AES_128_KEY_SIZE,
187+
kat_iv, AES_DECRYPTION);
188+
}
189+
if (ret == 0) {
190+
ret = wc_AesCbcDecrypt(&dec, out, kat_ct, KAT_PT_SZ);
191+
}
192+
if (ret == 0) {
193+
ok = kat_check("decrypted plaintext", out, kat_pt, KAT_PT_SZ) && ok;
194+
}
195+
else {
196+
printf("FAIL: decrypt KAT op: %d\n", ret);
197+
ok = 0;
198+
}
199+
wc_AesFree(&dec);
200+
201+
cleanup:
202+
if (inited) {
203+
wolfCrypt_Cleanup();
204+
}
205+
206+
if (ok) {
207+
printf("TROPIC01 CBC KAT: PASS\n");
208+
return 0;
209+
}
210+
printf("TROPIC01 CBC KAT: FAIL\n");
211+
return 1;
212+
}

0 commit comments

Comments
 (0)