Skip to content

Commit d9786fe

Browse files
committed
wolfCrypt SRAM PUF Support
Add SRAM PUF (Physically Unclonable Function) support to wolfCrypt. Derives device-unique cryptographic keys from the power-on state of SRAM memory using a BCH(127,64,t=10) fuzzy extractor with HKDF key derivation. - **wolfCrypt PUF API** (`wolfcrypt/src/puf.c`, `wolfssl/wolfcrypt/puf.h`) - `wc_PufInit`, `wc_PufReadSram`, `wc_PufEnroll`, `wc_PufReconstruct` - `wc_PufDeriveKey` (HKDF-SHA256), `wc_PufGetIdentity` (SHA-256 device fingerprint) - `wc_PufZeroize` (secure context cleanup) - `wc_PufSetTestData` (synthetic SRAM for testing without hardware) - **BCH(127,64,t=10) error-correcting codec** - corrects up to 10 bit flips per 127-bit codeword across 16 codewords - **`WC_PUF_SHA3` build option** - select SHA3-256 instead of SHA-256 for identity hash and HKDF (default: SHA-256) - **Precomputed GF(2^7) tables** - `const` arrays in `.rodata` (no runtime init, thread-safe, flash-resident on embedded) - `./configure --enable-puf` (auto-enables HKDF dependency) - CMake: `WOLFSSL_PUF=yes` - `WOLFSSL_USER_SETTINGS`: define `WOLFSSL_PUF` and `WOLFSSL_PUF_SRAM` - See wolfssl-examples/puf for example implementation on STM32 NUCLEO-H563ZI (Cortex-M33, STM32H563ZI) - Supports test mode (synthetic SRAM) - Builds to ~13KB `.elf` - Tested on NUCLEO-H563ZI: enrollment, noisy reconstruction, key derivation all pass - `.github/workflows/puf.yml`: host build + test workflow for PUF feature - Doxygen API docs for all 8 public functions - PUF group added to `doxygen_groups.h`
1 parent abfff1e commit d9786fe

File tree

15 files changed

+1283
-2
lines changed

15 files changed

+1283
-2
lines changed

.github/workflows/puf.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: PUF Tests
2+
3+
# START OF COMMON SECTION
4+
on:
5+
push:
6+
branches: [ 'master', 'main', 'release/**' ]
7+
pull_request:
8+
branches: [ '*' ]
9+
10+
concurrency:
11+
group: ${{ github.workflow }}-${{ github.ref }}
12+
cancel-in-progress: true
13+
# END OF COMMON SECTION
14+
15+
jobs:
16+
puf_host_test:
17+
name: PUF host test
18+
if: github.repository_owner == 'wolfssl'
19+
runs-on: ubuntu-24.04
20+
timeout-minutes: 6
21+
steps:
22+
- uses: actions/checkout@v4
23+
name: Checkout wolfSSL
24+
25+
- name: Build and test PUF
26+
run: |
27+
./autogen.sh
28+
./configure --enable-puf --enable-puf-test
29+
make
30+
./wolfcrypt/test/testwolfcrypt
31+
32+
- name: Print errors
33+
if: ${{ failure() }}
34+
run: |
35+
if [ -f test-suite.log ] ; then
36+
cat test-suite.log
37+
fi

.wolfssl_known_macro_extras

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,7 @@ WC_NO_VERBOSE_RNG
649649
WC_PKCS11_FIND_WITH_ID_ONLY
650650
WC_PKCS12_PBKDF_USING_MP_API
651651
WC_PROTECT_ENCRYPTED_MEM
652+
WC_PUF_SHA3
652653
WC_RNG_BANK_NO_DEFAULT_SUPPORT
653654
WC_RNG_BLOCKING
654655
WC_RSA_NONBLOCK

CMakeLists.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1730,6 +1730,27 @@ endif()
17301730

17311731
# TODO: - XCHACHA
17321732

1733+
# SRAM PUF
1734+
add_option("WOLFSSL_PUF"
1735+
"Enable SRAM PUF support (default: disabled)"
1736+
"no" "yes;no")
1737+
1738+
if(WOLFSSL_PUF)
1739+
list(APPEND WOLFSSL_DEFINITIONS
1740+
"-DWOLFSSL_PUF"
1741+
"-DWOLFSSL_PUF_SRAM")
1742+
override_cache(WOLFSSL_HKDF "yes")
1743+
endif()
1744+
1745+
# PUF test mode (synthetic SRAM data injection)
1746+
add_option("WOLFSSL_PUF_TEST"
1747+
"Enable PUF test mode with synthetic data (default: disabled)"
1748+
"no" "yes;no")
1749+
1750+
if(WOLFSSL_PUF_TEST)
1751+
list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_PUF_TEST")
1752+
endif()
1753+
17331754
# Hash DRBG
17341755
add_option("WOLFSSL_HASH_DRBG"
17351756
"Enable Hash DRBG support (default: enabled)"

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ certificate #3389). FIPS 140-3 validated (Certificate #4718). For additional
1818
information, visit the [wolfCrypt FIPS FAQ](https://www.wolfssl.com/license/fips/)
1919
or contact fips@wolfssl.com.
2020

21+
wolfCrypt also includes support for deriving device-unique keys from hardware entropy
22+
(`--enable-puf`). An example exists at
23+
[SRAM PUF](https://github.com/wolfSSL/wolfssl-examples/tree/master/puf).
24+
2125
## Why Choose wolfSSL?
2226

2327
There are many reasons to choose wolfSSL as your embedded, desktop, mobile, or

cmake/functions.cmake

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,9 @@ function(generate_build_flags)
345345
if(WOLFSSL_HPKE OR WOLFSSL_USER_SETTINGS)
346346
set(BUILD_HPKE "yes" PARENT_SCOPE)
347347
endif()
348+
if(WOLFSSL_PUF OR WOLFSSL_USER_SETTINGS)
349+
set(BUILD_PUF "yes" PARENT_SCOPE)
350+
endif()
348351

349352
set(BUILD_FLAGS_GENERATED "yes" PARENT_SCOPE)
350353
endfunction()
@@ -1202,6 +1205,10 @@ function(generate_lib_src_list LIB_SOURCES)
12021205
list(APPEND LIB_SOURCES wolfcrypt/src/hpke.c)
12031206
endif()
12041207

1208+
if(BUILD_PUF)
1209+
list(APPEND LIB_SOURCES wolfcrypt/src/puf.c)
1210+
endif()
1211+
12051212
set(LIB_SOURCES ${LIB_SOURCES} PARENT_SCOPE)
12061213
endfunction()
12071214

configure.ac

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7176,6 +7176,32 @@ then
71767176
AM_CFLAGS="$AM_CFLAGS -DHAVE_ASCON"
71777177
fi
71787178
7179+
# PUF
7180+
AC_ARG_ENABLE([puf],
7181+
[AS_HELP_STRING([--enable-puf],[Enable SRAM PUF support (default: disabled)])],
7182+
[ ENABLED_PUF=$enableval ],
7183+
[ ENABLED_PUF=no ]
7184+
)
7185+
7186+
if test "$ENABLED_PUF" = "yes"
7187+
then
7188+
AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_PUF -DWOLFSSL_PUF_SRAM"
7189+
AS_IF([test "$ENABLED_HKDF" != "yes"],
7190+
[ENABLED_HKDF="yes"; AM_CFLAGS="$AM_CFLAGS -DHAVE_HKDF"])
7191+
fi
7192+
7193+
# PUF test mode
7194+
AC_ARG_ENABLE([puf-test],
7195+
[AS_HELP_STRING([--enable-puf-test],[Enable PUF test mode with synthetic data (default: disabled)])],
7196+
[ ENABLED_PUF_TEST=$enableval ],
7197+
[ ENABLED_PUF_TEST=no ]
7198+
)
7199+
7200+
if test "$ENABLED_PUF_TEST" = "yes"
7201+
then
7202+
AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_PUF_TEST"
7203+
fi
7204+
71797205
# Hash DRBG
71807206
AC_ARG_ENABLE([hashdrbg],
71817207
[AS_HELP_STRING([--enable-hashdrbg],[Enable Hash DRBG support (default: enabled)])],
@@ -11552,6 +11578,7 @@ AM_CONDITIONAL([BUILD_CHACHA],[test "x$ENABLED_CHACHA" = "xyes" || test "x$ENABL
1155211578
AM_CONDITIONAL([BUILD_CHACHA_NOASM],[test "$ENABLED_CHACHA" = "noasm"])
1155311579
AM_CONDITIONAL([BUILD_XCHACHA],[test "x$ENABLED_XCHACHA" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"])
1155411580
AM_CONDITIONAL([BUILD_ASCON],[test "x$ENABLED_ASCON" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"])
11581+
AM_CONDITIONAL([BUILD_PUF],[test "x$ENABLED_PUF" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"])
1155511582
AM_CONDITIONAL([BUILD_SM2],[test "x$ENABLED_SM2" != "xno" || test "x$ENABLED_USERSETTINGS" = "xyes"])
1155611583
AM_CONDITIONAL([BUILD_SM3],[test "x$ENABLED_SM3" != "xno" || test "x$ENABLED_USERSETTINGS" = "xyes"])
1155711584
AM_CONDITIONAL([BUILD_SM4],[test "x$ENABLED_SM4" != "xno" || test "x$ENABLED_USERSETTINGS" = "xyes"])
@@ -12218,6 +12245,7 @@ echo " * AutoSAR : $ENABLED_AUTOSAR"
1221812245
echo " * ML-KEM standalone: $ENABLED_MLKEM_STANDALONE"
1221912246
echo " * PQ/T hybrids: $ENABLED_PQC_HYBRIDS"
1222012247
echo " * Extra PQ/T hybrids: $ENABLED_EXTRA_PQC_HYBRIDS"
12248+
echo " * PUF: $ENABLED_PUF"
1222112249
echo ""
1222212250
echo "---"
1222312251

doc/dox_comments/header_files/doxygen_groups.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@
202202
\defgroup PKCS11 Algorithms - PKCS11
203203
\defgroup Password Algorithms - Password Based
204204
\defgroup Poly1305 Algorithms - Poly1305
205+
\defgroup PUF Algorithms - PUF
205206
\defgroup RIPEMD Algorithms - RIPEMD
206207
\defgroup RSA Algorithms - RSA
207208
\defgroup SHA Algorithms - SHA 128/224/256/384/512
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
/*!
2+
\ingroup PUF
3+
4+
For a complete bare-metal example (tested on NUCLEO-H563ZI), see
5+
https://github.com/wolfSSL/wolfssl-examples/tree/master/puf
6+
*/
7+
8+
/*!
9+
\ingroup PUF
10+
11+
\brief Initialize a wc_PufCtx structure, zeroing all fields.
12+
Must be called before any other PUF operations.
13+
14+
\return 0 on success
15+
\return BAD_FUNC_ARG if ctx is NULL
16+
17+
\param ctx pointer to wc_PufCtx structure to initialize
18+
19+
_Example_
20+
\code
21+
wc_PufCtx ctx;
22+
ret = wc_PufInit(&ctx);
23+
\endcode
24+
25+
\sa wc_PufReadSram
26+
\sa wc_PufEnroll
27+
\sa wc_PufZeroize
28+
*/
29+
int wc_PufInit(wc_PufCtx* ctx);
30+
31+
/*!
32+
\ingroup PUF
33+
34+
\brief Read raw SRAM data into the PUF context. The sramAddr should
35+
point to a NOLOAD linker section to preserve the power-on state.
36+
37+
\return 0 on success
38+
\return BAD_FUNC_ARG if ctx or sramAddr is NULL
39+
\return PUF_READ_E if sramSz < WC_PUF_RAW_BYTES
40+
41+
\param ctx pointer to wc_PufCtx structure
42+
\param sramAddr pointer to raw SRAM memory region
43+
\param sramSz size of SRAM buffer (must be >= WC_PUF_RAW_BYTES)
44+
45+
_Example_
46+
\code
47+
__attribute__((section(".puf_sram")))
48+
static volatile uint8_t puf_sram[256];
49+
wc_PufReadSram(&ctx, (const byte*)puf_sram, sizeof(puf_sram));
50+
\endcode
51+
52+
\sa wc_PufInit
53+
\sa wc_PufEnroll
54+
\sa wc_PufReconstruct
55+
*/
56+
int wc_PufReadSram(wc_PufCtx* ctx, const byte* sramAddr, word32 sramSz);
57+
58+
/*!
59+
\ingroup PUF
60+
61+
\brief Perform PUF enrollment. Encodes raw SRAM using BCH(127,64,t=10)
62+
and generates public helper data. After enrollment the context is ready
63+
for key derivation and identity retrieval.
64+
65+
\return 0 on success
66+
\return BAD_FUNC_ARG if ctx is NULL
67+
\return PUF_ENROLL_E if enrollment fails
68+
69+
\param ctx pointer to wc_PufCtx (must have SRAM data loaded)
70+
71+
_Example_
72+
\code
73+
wc_PufEnroll(&ctx);
74+
XMEMCPY(helperData, ctx.helperData, WC_PUF_HELPER_BYTES);
75+
\endcode
76+
77+
\sa wc_PufReadSram
78+
\sa wc_PufReconstruct
79+
\sa wc_PufDeriveKey
80+
*/
81+
int wc_PufEnroll(wc_PufCtx* ctx);
82+
83+
/*!
84+
\ingroup PUF
85+
86+
\brief Reconstruct stable PUF bits from noisy SRAM using stored helper
87+
data. BCH error correction (t=10) corrects up to 10 bit flips per
88+
127-bit codeword.
89+
90+
\return 0 on success
91+
\return BAD_FUNC_ARG if ctx or helperData is NULL
92+
\return PUF_RECONSTRUCT_E on failure (too many bit errors or helperSz
93+
too small)
94+
95+
\param ctx pointer to wc_PufCtx (must have SRAM data loaded)
96+
\param helperData pointer to helper data from previous enrollment
97+
\param helperSz size of helper data (>= WC_PUF_HELPER_BYTES)
98+
99+
_Example_
100+
\code
101+
wc_PufReconstruct(&ctx, helperData, sizeof(helperData));
102+
\endcode
103+
104+
\sa wc_PufEnroll
105+
\sa wc_PufDeriveKey
106+
\sa wc_PufGetIdentity
107+
*/
108+
int wc_PufReconstruct(wc_PufCtx* ctx, const byte* helperData, word32 helperSz);
109+
110+
/*!
111+
\ingroup PUF
112+
113+
\brief Derive a cryptographic key from PUF stable bits using HKDF.
114+
Uses SHA-256 by default, or SHA3-256 when WC_PUF_SHA3 is defined.
115+
The info parameter provides domain separation for multiple keys.
116+
Requires HAVE_HKDF.
117+
118+
\return 0 on success
119+
\return BAD_FUNC_ARG if ctx or key is NULL, or keySz is 0
120+
\return PUF_DERIVE_KEY_E if PUF not ready or HKDF fails
121+
122+
\param ctx pointer to wc_PufCtx (must be enrolled or reconstructed)
123+
\param info optional context info for domain separation (may be NULL)
124+
\param infoSz size of info in bytes
125+
\param key output buffer for derived key
126+
\param keySz desired key size in bytes
127+
128+
_Example_
129+
\code
130+
byte key[32];
131+
const byte info[] = "my-app-key";
132+
wc_PufDeriveKey(&ctx, info, sizeof(info), key, sizeof(key));
133+
\endcode
134+
135+
\sa wc_PufEnroll
136+
\sa wc_PufReconstruct
137+
\sa wc_PufGetIdentity
138+
*/
139+
int wc_PufDeriveKey(wc_PufCtx* ctx, const byte* info, word32 infoSz,
140+
byte* key, word32 keySz);
141+
142+
/*!
143+
\ingroup PUF
144+
145+
\brief Retrieve the device identity hash (SHA-256 or SHA3-256 of stable
146+
bits). Deterministic for a given device.
147+
148+
\return 0 on success
149+
\return BAD_FUNC_ARG if ctx or id is NULL
150+
\return PUF_IDENTITY_E if PUF not ready or idSz < WC_PUF_ID_SZ
151+
152+
\param ctx pointer to wc_PufCtx (must be enrolled or reconstructed)
153+
\param id output buffer for identity hash
154+
\param idSz size of id buffer (>= WC_PUF_ID_SZ, 32 bytes)
155+
156+
_Example_
157+
\code
158+
byte identity[WC_PUF_ID_SZ];
159+
wc_PufGetIdentity(&ctx, identity, sizeof(identity));
160+
\endcode
161+
162+
\sa wc_PufEnroll
163+
\sa wc_PufReconstruct
164+
\sa wc_PufDeriveKey
165+
*/
166+
int wc_PufGetIdentity(wc_PufCtx* ctx, byte* id, word32 idSz);
167+
168+
/*!
169+
\ingroup PUF
170+
171+
\brief Securely zeroize all sensitive data in the PUF context using
172+
ForceZero. Call when PUF is no longer needed.
173+
174+
\return 0 on success
175+
\return BAD_FUNC_ARG if ctx is NULL
176+
177+
\param ctx pointer to wc_PufCtx to zeroize
178+
179+
_Example_
180+
\code
181+
wc_PufZeroize(&ctx);
182+
\endcode
183+
184+
\sa wc_PufInit
185+
*/
186+
int wc_PufZeroize(wc_PufCtx* ctx);
187+
188+
/*!
189+
\ingroup PUF
190+
191+
\brief Inject synthetic SRAM test data for testing without hardware.
192+
Only available when WOLFSSL_PUF_TEST is defined.
193+
194+
\return 0 on success
195+
\return BAD_FUNC_ARG if ctx or data is NULL
196+
\return PUF_READ_E if sz < WC_PUF_RAW_BYTES
197+
198+
\param ctx pointer to wc_PufCtx
199+
\param data pointer to synthetic SRAM data
200+
\param sz size of data (>= WC_PUF_RAW_BYTES, 256 bytes)
201+
202+
_Example_
203+
\code
204+
byte testSram[WC_PUF_RAW_BYTES];
205+
wc_PufSetTestData(&ctx, testSram, sizeof(testSram));
206+
\endcode
207+
208+
\sa wc_PufInit
209+
\sa wc_PufReadSram
210+
*/
211+
int wc_PufSetTestData(wc_PufCtx* ctx, const byte* data, word32 sz);

src/include.am

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,6 +1352,10 @@ if BUILD_ASCON
13521352
src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/ascon.c
13531353
endif
13541354

1355+
if BUILD_PUF
1356+
src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/puf.c
1357+
endif
1358+
13551359
if !BUILD_INLINE
13561360
src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/misc.c
13571361
endif

0 commit comments

Comments
 (0)