Skip to content

Commit 6a0303e

Browse files
Merge pull request #10066 from dgarske/wc_puf
wolfCrypt SRAM PUF Support
2 parents 53e3521 + e05ce26 commit 6a0303e

15 files changed

Lines changed: 1394 additions & 2 deletions

File tree

.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
@@ -652,6 +652,7 @@ WC_NO_VERBOSE_RNG
652652
WC_PKCS11_FIND_WITH_ID_ONLY
653653
WC_PKCS12_PBKDF_USING_MP_API
654654
WC_PROTECT_ENCRYPTED_MEM
655+
WC_PUF_SHA3
655656
WC_RNG_BANK_NO_DEFAULT_SUPPORT
656657
WC_RNG_BLOCKING
657658
WC_RSA_NONBLOCK

CMakeLists.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1772,6 +1772,28 @@ endif()
17721772

17731773
# TODO: - XCHACHA
17741774

1775+
# SRAM PUF
1776+
add_option("WOLFSSL_PUF"
1777+
"Enable SRAM PUF support (default: disabled)"
1778+
"no" "yes;no")
1779+
1780+
if(WOLFSSL_PUF)
1781+
list(APPEND WOLFSSL_DEFINITIONS
1782+
"-DWOLFSSL_PUF"
1783+
"-DWOLFSSL_PUF_SRAM"
1784+
"-DHAVE_HKDF")
1785+
override_cache(WOLFSSL_HKDF "yes")
1786+
endif()
1787+
1788+
# PUF test mode (synthetic SRAM data injection)
1789+
add_option("WOLFSSL_PUF_TEST"
1790+
"Enable PUF test mode with synthetic data (default: disabled)"
1791+
"no" "yes;no")
1792+
1793+
if(WOLFSSL_PUF_TEST)
1794+
list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_PUF_TEST")
1795+
endif()
1796+
17751797
# Hash DRBG
17761798
add_option("WOLFSSL_HASH_DRBG"
17771799
"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
@@ -348,6 +348,9 @@ function(generate_build_flags)
348348
if(WOLFSSL_SHE AND NOT WOLFSSL_SHE STREQUAL "no")
349349
set(BUILD_SHE "yes" PARENT_SCOPE)
350350
endif()
351+
if(WOLFSSL_PUF OR WOLFSSL_USER_SETTINGS)
352+
set(BUILD_PUF "yes" PARENT_SCOPE)
353+
endif()
351354

352355
set(BUILD_FLAGS_GENERATED "yes" PARENT_SCOPE)
353356
endfunction()
@@ -1209,6 +1212,10 @@ function(generate_lib_src_list LIB_SOURCES)
12091212
list(APPEND LIB_SOURCES wolfcrypt/src/hpke.c)
12101213
endif()
12111214

1215+
if(BUILD_PUF)
1216+
list(APPEND LIB_SOURCES wolfcrypt/src/puf.c)
1217+
endif()
1218+
12121219
set(LIB_SOURCES ${LIB_SOURCES} PARENT_SCOPE)
12131220
endfunction()
12141221

configure.ac

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7222,6 +7222,32 @@ then
72227222
AM_CFLAGS="$AM_CFLAGS -DHAVE_ASCON"
72237223
fi
72247224
7225+
# PUF
7226+
AC_ARG_ENABLE([puf],
7227+
[AS_HELP_STRING([--enable-puf],[Enable SRAM PUF support (default: disabled)])],
7228+
[ ENABLED_PUF=$enableval ],
7229+
[ ENABLED_PUF=no ]
7230+
)
7231+
7232+
if test "$ENABLED_PUF" = "yes"
7233+
then
7234+
AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_PUF -DWOLFSSL_PUF_SRAM"
7235+
AS_IF([test "$ENABLED_HKDF" != "yes"],
7236+
[ENABLED_HKDF="yes"; AM_CFLAGS="$AM_CFLAGS -DHAVE_HKDF"])
7237+
fi
7238+
7239+
# PUF test mode
7240+
AC_ARG_ENABLE([puf-test],
7241+
[AS_HELP_STRING([--enable-puf-test],[Enable PUF test mode with synthetic data (default: disabled)])],
7242+
[ ENABLED_PUF_TEST=$enableval ],
7243+
[ ENABLED_PUF_TEST=no ]
7244+
)
7245+
7246+
if test "$ENABLED_PUF_TEST" = "yes"
7247+
then
7248+
AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_PUF_TEST"
7249+
fi
7250+
72257251
# Hash DRBG
72267252
AC_ARG_ENABLE([hashdrbg],
72277253
[AS_HELP_STRING([--enable-hashdrbg],[Enable Hash DRBG support (default: enabled)])],
@@ -11610,6 +11636,7 @@ AM_CONDITIONAL([BUILD_CHACHA],[test "x$ENABLED_CHACHA" = "xyes" || test "x$ENABL
1161011636
AM_CONDITIONAL([BUILD_CHACHA_NOASM],[test "$ENABLED_CHACHA" = "noasm"])
1161111637
AM_CONDITIONAL([BUILD_XCHACHA],[test "x$ENABLED_XCHACHA" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"])
1161211638
AM_CONDITIONAL([BUILD_ASCON],[test "x$ENABLED_ASCON" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"])
11639+
AM_CONDITIONAL([BUILD_PUF],[test "x$ENABLED_PUF" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"])
1161311640
AM_CONDITIONAL([BUILD_SM2],[test "x$ENABLED_SM2" != "xno" || test "x$ENABLED_USERSETTINGS" = "xyes"])
1161411641
AM_CONDITIONAL([BUILD_SM3],[test "x$ENABLED_SM3" != "xno" || test "x$ENABLED_USERSETTINGS" = "xyes"])
1161511642
AM_CONDITIONAL([BUILD_SM4],[test "x$ENABLED_SM4" != "xno" || test "x$ENABLED_USERSETTINGS" = "xyes"])
@@ -12276,6 +12303,7 @@ echo " * AutoSAR : $ENABLED_AUTOSAR"
1227612303
echo " * ML-KEM standalone: $ENABLED_MLKEM_STANDALONE"
1227712304
echo " * PQ/T hybrids: $ENABLED_PQC_HYBRIDS"
1227812305
echo " * Extra PQ/T hybrids: $ENABLED_EXTRA_PQC_HYBRIDS"
12306+
echo " * PUF: $ENABLED_PUF"
1227912307
echo ""
1228012308
echo "---"
1228112309

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: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
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+
when NULL, infoSz is treated as 0)
125+
\param infoSz size of info in bytes
126+
\param key output buffer for derived key
127+
\param keySz desired key size in bytes
128+
129+
_Example_
130+
\code
131+
byte key[32];
132+
const byte info[] = "my-app-key";
133+
wc_PufDeriveKey(&ctx, info, sizeof(info), key, sizeof(key));
134+
\endcode
135+
136+
\sa wc_PufEnroll
137+
\sa wc_PufReconstruct
138+
\sa wc_PufGetIdentity
139+
*/
140+
int wc_PufDeriveKey(wc_PufCtx* ctx, const byte* info, word32 infoSz,
141+
byte* key, word32 keySz);
142+
143+
/*!
144+
\ingroup PUF
145+
146+
\brief Retrieve the device identity hash (SHA-256 or SHA3-256 of stable
147+
bits). Deterministic for a given device.
148+
149+
\return 0 on success
150+
\return BAD_FUNC_ARG if ctx or id is NULL
151+
\return PUF_IDENTITY_E if PUF not ready or idSz < WC_PUF_ID_SZ
152+
153+
\param ctx pointer to wc_PufCtx (must be enrolled or reconstructed)
154+
\param id output buffer for identity hash
155+
\param idSz size of id buffer (>= WC_PUF_ID_SZ, 32 bytes)
156+
157+
_Example_
158+
\code
159+
byte identity[WC_PUF_ID_SZ];
160+
wc_PufGetIdentity(&ctx, identity, sizeof(identity));
161+
\endcode
162+
163+
\sa wc_PufEnroll
164+
\sa wc_PufReconstruct
165+
\sa wc_PufDeriveKey
166+
*/
167+
int wc_PufGetIdentity(wc_PufCtx* ctx, byte* id, word32 idSz);
168+
169+
/*!
170+
\ingroup PUF
171+
172+
\brief Securely zeroize all sensitive data in the PUF context using
173+
ForceZero. Call when PUF is no longer needed.
174+
175+
\return 0 on success
176+
\return BAD_FUNC_ARG if ctx is NULL
177+
178+
\param ctx pointer to wc_PufCtx to zeroize
179+
180+
_Example_
181+
\code
182+
wc_PufZeroize(&ctx);
183+
\endcode
184+
185+
\sa wc_PufInit
186+
*/
187+
int wc_PufZeroize(wc_PufCtx* ctx);
188+
189+
/*!
190+
\ingroup PUF
191+
192+
\brief Inject synthetic SRAM test data for testing without hardware.
193+
Only available when WOLFSSL_PUF_TEST is defined.
194+
195+
\return 0 on success
196+
\return BAD_FUNC_ARG if ctx or data is NULL
197+
\return PUF_READ_E if sz < WC_PUF_RAW_BYTES
198+
199+
\param ctx pointer to wc_PufCtx
200+
\param data pointer to synthetic SRAM data
201+
\param sz size of data (>= WC_PUF_RAW_BYTES, 256 bytes)
202+
203+
_Example_
204+
\code
205+
byte testSram[WC_PUF_RAW_BYTES];
206+
wc_PufSetTestData(&ctx, testSram, sizeof(testSram));
207+
\endcode
208+
209+
\sa wc_PufInit
210+
\sa wc_PufReadSram
211+
*/
212+
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
@@ -1372,6 +1372,10 @@ if BUILD_ASCON
13721372
src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/ascon.c
13731373
endif
13741374

1375+
if BUILD_PUF
1376+
src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/puf.c
1377+
endif
1378+
13751379
if !BUILD_INLINE
13761380
src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/misc.c
13771381
endif

0 commit comments

Comments
 (0)