Merge pull request #10066 from dgarske/wc_puf

wolfCrypt SRAM PUF Support
This commit is contained in:
JacobBarthelmeh
2026-04-23 14:28:37 -06:00
committed by GitHub
15 changed files with 1394 additions and 2 deletions
+37
View File
@@ -0,0 +1,37 @@
name: PUF Tests
# START OF COMMON SECTION
on:
push:
branches: [ 'master', 'main', 'release/**' ]
pull_request:
branches: [ '*' ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# END OF COMMON SECTION
jobs:
puf_host_test:
name: PUF host test
if: github.repository_owner == 'wolfssl'
runs-on: ubuntu-24.04
timeout-minutes: 6
steps:
- uses: actions/checkout@v4
name: Checkout wolfSSL
- name: Build and test PUF
run: |
./autogen.sh
./configure --enable-puf --enable-puf-test
make
./wolfcrypt/test/testwolfcrypt
- name: Print errors
if: ${{ failure() }}
run: |
if [ -f test-suite.log ] ; then
cat test-suite.log
fi
+1
View File
@@ -652,6 +652,7 @@ WC_NO_VERBOSE_RNG
WC_PKCS11_FIND_WITH_ID_ONLY
WC_PKCS12_PBKDF_USING_MP_API
WC_PROTECT_ENCRYPTED_MEM
WC_PUF_SHA3
WC_RNG_BANK_NO_DEFAULT_SUPPORT
WC_RNG_BLOCKING
WC_RSA_NONBLOCK
+22
View File
@@ -1772,6 +1772,28 @@ endif()
# TODO: - XCHACHA
# SRAM PUF
add_option("WOLFSSL_PUF"
"Enable SRAM PUF support (default: disabled)"
"no" "yes;no")
if(WOLFSSL_PUF)
list(APPEND WOLFSSL_DEFINITIONS
"-DWOLFSSL_PUF"
"-DWOLFSSL_PUF_SRAM"
"-DHAVE_HKDF")
override_cache(WOLFSSL_HKDF "yes")
endif()
# PUF test mode (synthetic SRAM data injection)
add_option("WOLFSSL_PUF_TEST"
"Enable PUF test mode with synthetic data (default: disabled)"
"no" "yes;no")
if(WOLFSSL_PUF_TEST)
list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_PUF_TEST")
endif()
# Hash DRBG
add_option("WOLFSSL_HASH_DRBG"
"Enable Hash DRBG support (default: enabled)"
+4
View File
@@ -18,6 +18,10 @@ certificate #3389). FIPS 140-3 validated (Certificate #4718). For additional
information, visit the [wolfCrypt FIPS FAQ](https://www.wolfssl.com/license/fips/)
or contact fips@wolfssl.com.
wolfCrypt also includes support for deriving device-unique keys from hardware entropy
(`--enable-puf`). An example exists at
[SRAM PUF](https://github.com/wolfSSL/wolfssl-examples/tree/master/puf).
## Why Choose wolfSSL?
There are many reasons to choose wolfSSL as your embedded, desktop, mobile, or
+7
View File
@@ -348,6 +348,9 @@ function(generate_build_flags)
if(WOLFSSL_SHE AND NOT WOLFSSL_SHE STREQUAL "no")
set(BUILD_SHE "yes" PARENT_SCOPE)
endif()
if(WOLFSSL_PUF OR WOLFSSL_USER_SETTINGS)
set(BUILD_PUF "yes" PARENT_SCOPE)
endif()
set(BUILD_FLAGS_GENERATED "yes" PARENT_SCOPE)
endfunction()
@@ -1209,6 +1212,10 @@ function(generate_lib_src_list LIB_SOURCES)
list(APPEND LIB_SOURCES wolfcrypt/src/hpke.c)
endif()
if(BUILD_PUF)
list(APPEND LIB_SOURCES wolfcrypt/src/puf.c)
endif()
set(LIB_SOURCES ${LIB_SOURCES} PARENT_SCOPE)
endfunction()
+28
View File
@@ -7222,6 +7222,32 @@ then
AM_CFLAGS="$AM_CFLAGS -DHAVE_ASCON"
fi
# PUF
AC_ARG_ENABLE([puf],
[AS_HELP_STRING([--enable-puf],[Enable SRAM PUF support (default: disabled)])],
[ ENABLED_PUF=$enableval ],
[ ENABLED_PUF=no ]
)
if test "$ENABLED_PUF" = "yes"
then
AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_PUF -DWOLFSSL_PUF_SRAM"
AS_IF([test "$ENABLED_HKDF" != "yes"],
[ENABLED_HKDF="yes"; AM_CFLAGS="$AM_CFLAGS -DHAVE_HKDF"])
fi
# PUF test mode
AC_ARG_ENABLE([puf-test],
[AS_HELP_STRING([--enable-puf-test],[Enable PUF test mode with synthetic data (default: disabled)])],
[ ENABLED_PUF_TEST=$enableval ],
[ ENABLED_PUF_TEST=no ]
)
if test "$ENABLED_PUF_TEST" = "yes"
then
AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_PUF_TEST"
fi
# Hash DRBG
AC_ARG_ENABLE([hashdrbg],
[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
AM_CONDITIONAL([BUILD_CHACHA_NOASM],[test "$ENABLED_CHACHA" = "noasm"])
AM_CONDITIONAL([BUILD_XCHACHA],[test "x$ENABLED_XCHACHA" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"])
AM_CONDITIONAL([BUILD_ASCON],[test "x$ENABLED_ASCON" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"])
AM_CONDITIONAL([BUILD_PUF],[test "x$ENABLED_PUF" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"])
AM_CONDITIONAL([BUILD_SM2],[test "x$ENABLED_SM2" != "xno" || test "x$ENABLED_USERSETTINGS" = "xyes"])
AM_CONDITIONAL([BUILD_SM3],[test "x$ENABLED_SM3" != "xno" || test "x$ENABLED_USERSETTINGS" = "xyes"])
AM_CONDITIONAL([BUILD_SM4],[test "x$ENABLED_SM4" != "xno" || test "x$ENABLED_USERSETTINGS" = "xyes"])
@@ -12276,6 +12303,7 @@ echo " * AutoSAR : $ENABLED_AUTOSAR"
echo " * ML-KEM standalone: $ENABLED_MLKEM_STANDALONE"
echo " * PQ/T hybrids: $ENABLED_PQC_HYBRIDS"
echo " * Extra PQ/T hybrids: $ENABLED_EXTRA_PQC_HYBRIDS"
echo " * PUF: $ENABLED_PUF"
echo ""
echo "---"
@@ -202,6 +202,7 @@
\defgroup PKCS11 Algorithms - PKCS11
\defgroup Password Algorithms - Password Based
\defgroup Poly1305 Algorithms - Poly1305
\defgroup PUF Algorithms - PUF
\defgroup RIPEMD Algorithms - RIPEMD
\defgroup RSA Algorithms - RSA
\defgroup SHA Algorithms - SHA 128/224/256/384/512
+212
View File
@@ -0,0 +1,212 @@
/*!
\ingroup PUF
For a complete bare-metal example (tested on NUCLEO-H563ZI), see
https://github.com/wolfSSL/wolfssl-examples/tree/master/puf
*/
/*!
\ingroup PUF
\brief Initialize a wc_PufCtx structure, zeroing all fields.
Must be called before any other PUF operations.
\return 0 on success
\return BAD_FUNC_ARG if ctx is NULL
\param ctx pointer to wc_PufCtx structure to initialize
_Example_
\code
wc_PufCtx ctx;
ret = wc_PufInit(&ctx);
\endcode
\sa wc_PufReadSram
\sa wc_PufEnroll
\sa wc_PufZeroize
*/
int wc_PufInit(wc_PufCtx* ctx);
/*!
\ingroup PUF
\brief Read raw SRAM data into the PUF context. The sramAddr should
point to a NOLOAD linker section to preserve the power-on state.
\return 0 on success
\return BAD_FUNC_ARG if ctx or sramAddr is NULL
\return PUF_READ_E if sramSz < WC_PUF_RAW_BYTES
\param ctx pointer to wc_PufCtx structure
\param sramAddr pointer to raw SRAM memory region
\param sramSz size of SRAM buffer (must be >= WC_PUF_RAW_BYTES)
_Example_
\code
__attribute__((section(".puf_sram")))
static volatile uint8_t puf_sram[256];
wc_PufReadSram(&ctx, (const byte*)puf_sram, sizeof(puf_sram));
\endcode
\sa wc_PufInit
\sa wc_PufEnroll
\sa wc_PufReconstruct
*/
int wc_PufReadSram(wc_PufCtx* ctx, const byte* sramAddr, word32 sramSz);
/*!
\ingroup PUF
\brief Perform PUF enrollment. Encodes raw SRAM using BCH(127,64,t=10)
and generates public helper data. After enrollment the context is ready
for key derivation and identity retrieval.
\return 0 on success
\return BAD_FUNC_ARG if ctx is NULL
\return PUF_ENROLL_E if enrollment fails
\param ctx pointer to wc_PufCtx (must have SRAM data loaded)
_Example_
\code
wc_PufEnroll(&ctx);
XMEMCPY(helperData, ctx.helperData, WC_PUF_HELPER_BYTES);
\endcode
\sa wc_PufReadSram
\sa wc_PufReconstruct
\sa wc_PufDeriveKey
*/
int wc_PufEnroll(wc_PufCtx* ctx);
/*!
\ingroup PUF
\brief Reconstruct stable PUF bits from noisy SRAM using stored helper
data. BCH error correction (t=10) corrects up to 10 bit flips per
127-bit codeword.
\return 0 on success
\return BAD_FUNC_ARG if ctx or helperData is NULL
\return PUF_RECONSTRUCT_E on failure (too many bit errors or helperSz
too small)
\param ctx pointer to wc_PufCtx (must have SRAM data loaded)
\param helperData pointer to helper data from previous enrollment
\param helperSz size of helper data (>= WC_PUF_HELPER_BYTES)
_Example_
\code
wc_PufReconstruct(&ctx, helperData, sizeof(helperData));
\endcode
\sa wc_PufEnroll
\sa wc_PufDeriveKey
\sa wc_PufGetIdentity
*/
int wc_PufReconstruct(wc_PufCtx* ctx, const byte* helperData, word32 helperSz);
/*!
\ingroup PUF
\brief Derive a cryptographic key from PUF stable bits using HKDF.
Uses SHA-256 by default, or SHA3-256 when WC_PUF_SHA3 is defined.
The info parameter provides domain separation for multiple keys.
Requires HAVE_HKDF.
\return 0 on success
\return BAD_FUNC_ARG if ctx or key is NULL, or keySz is 0
\return PUF_DERIVE_KEY_E if PUF not ready or HKDF fails
\param ctx pointer to wc_PufCtx (must be enrolled or reconstructed)
\param info optional context info for domain separation (may be NULL;
when NULL, infoSz is treated as 0)
\param infoSz size of info in bytes
\param key output buffer for derived key
\param keySz desired key size in bytes
_Example_
\code
byte key[32];
const byte info[] = "my-app-key";
wc_PufDeriveKey(&ctx, info, sizeof(info), key, sizeof(key));
\endcode
\sa wc_PufEnroll
\sa wc_PufReconstruct
\sa wc_PufGetIdentity
*/
int wc_PufDeriveKey(wc_PufCtx* ctx, const byte* info, word32 infoSz,
byte* key, word32 keySz);
/*!
\ingroup PUF
\brief Retrieve the device identity hash (SHA-256 or SHA3-256 of stable
bits). Deterministic for a given device.
\return 0 on success
\return BAD_FUNC_ARG if ctx or id is NULL
\return PUF_IDENTITY_E if PUF not ready or idSz < WC_PUF_ID_SZ
\param ctx pointer to wc_PufCtx (must be enrolled or reconstructed)
\param id output buffer for identity hash
\param idSz size of id buffer (>= WC_PUF_ID_SZ, 32 bytes)
_Example_
\code
byte identity[WC_PUF_ID_SZ];
wc_PufGetIdentity(&ctx, identity, sizeof(identity));
\endcode
\sa wc_PufEnroll
\sa wc_PufReconstruct
\sa wc_PufDeriveKey
*/
int wc_PufGetIdentity(wc_PufCtx* ctx, byte* id, word32 idSz);
/*!
\ingroup PUF
\brief Securely zeroize all sensitive data in the PUF context using
ForceZero. Call when PUF is no longer needed.
\return 0 on success
\return BAD_FUNC_ARG if ctx is NULL
\param ctx pointer to wc_PufCtx to zeroize
_Example_
\code
wc_PufZeroize(&ctx);
\endcode
\sa wc_PufInit
*/
int wc_PufZeroize(wc_PufCtx* ctx);
/*!
\ingroup PUF
\brief Inject synthetic SRAM test data for testing without hardware.
Only available when WOLFSSL_PUF_TEST is defined.
\return 0 on success
\return BAD_FUNC_ARG if ctx or data is NULL
\return PUF_READ_E if sz < WC_PUF_RAW_BYTES
\param ctx pointer to wc_PufCtx
\param data pointer to synthetic SRAM data
\param sz size of data (>= WC_PUF_RAW_BYTES, 256 bytes)
_Example_
\code
byte testSram[WC_PUF_RAW_BYTES];
wc_PufSetTestData(&ctx, testSram, sizeof(testSram));
\endcode
\sa wc_PufInit
\sa wc_PufReadSram
*/
int wc_PufSetTestData(wc_PufCtx* ctx, const byte* data, word32 sz);
+4
View File
@@ -1372,6 +1372,10 @@ if BUILD_ASCON
src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/ascon.c
endif
if BUILD_PUF
src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/puf.c
endif
if !BUILD_INLINE
src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/misc.c
endif
+18
View File
@@ -671,6 +671,24 @@ const char* wc_GetErrorString(int error)
case SEQ_OVERFLOW_E:
return "Sequence counter would overflow";
case PUF_INIT_E:
return "PUF initialization failed";
case PUF_READ_E:
return "PUF SRAM read failed";
case PUF_ENROLL_E:
return "PUF enrollment failed";
case PUF_RECONSTRUCT_E:
return "PUF reconstruction failed";
case PUF_DERIVE_KEY_E:
return "PUF key derivation failed";
case PUF_IDENTITY_E:
return "PUF identity retrieval failed";
case MAX_CODE_E:
case WC_SPAN1_MIN_CODE_E:
case MIN_CODE_E:
+698
View File
@@ -0,0 +1,698 @@
/* puf.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 <wolfssl/wolfcrypt/libwolfssl_sources.h>
#ifdef WOLFSSL_PUF
/* Currently only SRAM PUF is implemented. Other PUF types (ring-oscillator,
* arbiter) may be added in the future with their own guard macros. */
#if !defined(WOLFSSL_PUF_SRAM)
#define WOLFSSL_PUF_SRAM
#endif
/* PUF is not a FIPS-validated algorithm. The combination WOLFSSL_PUF +
* HAVE_FIPS is rejected at compile time by puf.h, so no per-translation-unit
* gate is needed here. */
#include <wolfssl/wolfcrypt/puf.h>
#include <wolfssl/wolfcrypt/error-crypt.h>
#include <wolfssl/wolfcrypt/hash.h>
#ifdef HAVE_HKDF
#include <wolfssl/wolfcrypt/hmac.h>
#endif
/* Hash algorithm selection: SHA3-256 or SHA-256 (default) */
#ifdef WC_PUF_SHA3
#if !defined(WOLFSSL_SHA3)
#error "WC_PUF_SHA3 requires WOLFSSL_SHA3 to be enabled"
#endif
#include <wolfssl/wolfcrypt/sha3.h>
#define WC_PUF_HASH_TYPE WC_SHA3_256
#define wc_PufHashDirect wc_Sha3_256Hash
#else
#ifdef NO_SHA256
#error "WOLFSSL_PUF requires SHA-256 or WC_PUF_SHA3"
#endif
#define WC_PUF_HASH_TYPE WC_SHA256
#define wc_PufHashDirect wc_Sha256Hash
#endif
#ifdef NO_INLINE
#include <wolfssl/wolfcrypt/misc.h>
#else
#define WOLFSSL_MISC_INCLUDED
#include <wolfcrypt/src/misc.c>
#endif
/* ========================================================================== */
/* BCH(127,64,t=10) codec over GF(2^7) */
/* ========================================================================== */
/* GF(2^7) arithmetic with primitive polynomial p(x) = x^7 + x^3 + 1 (0x89) */
#define GF_M 7
#define GF_SIZE (1 << GF_M) /* 128 */
#define GF_MASK (GF_SIZE - 1) /* 127 */
/* Precomputed GF(2^7) exp table: gf_exp[i] = alpha^i for i=0..127
* Generated with primitive polynomial 0x89 (x^7 + x^3 + 1).
* gf_exp[127] wraps to gf_exp[0] = 1. */
static const byte gf_exp[GF_SIZE] = {
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x09,
0x12, 0x24, 0x48, 0x19, 0x32, 0x64, 0x41, 0x0B,
0x16, 0x2C, 0x58, 0x39, 0x72, 0x6D, 0x53, 0x2F,
0x5E, 0x35, 0x6A, 0x5D, 0x33, 0x66, 0x45, 0x03,
0x06, 0x0C, 0x18, 0x30, 0x60, 0x49, 0x1B, 0x36,
0x6C, 0x51, 0x2B, 0x56, 0x25, 0x4A, 0x1D, 0x3A,
0x74, 0x61, 0x4B, 0x1F, 0x3E, 0x7C, 0x71, 0x6B,
0x5F, 0x37, 0x6E, 0x55, 0x23, 0x46, 0x05, 0x0A,
0x14, 0x28, 0x50, 0x29, 0x52, 0x2D, 0x5A, 0x3D,
0x7A, 0x7D, 0x73, 0x6F, 0x57, 0x27, 0x4E, 0x15,
0x2A, 0x54, 0x21, 0x42, 0x0D, 0x1A, 0x34, 0x68,
0x59, 0x3B, 0x76, 0x65, 0x43, 0x0F, 0x1E, 0x3C,
0x78, 0x79, 0x7B, 0x7F, 0x77, 0x67, 0x47, 0x07,
0x0E, 0x1C, 0x38, 0x70, 0x69, 0x5B, 0x3F, 0x7E,
0x75, 0x63, 0x4F, 0x17, 0x2E, 0x5C, 0x31, 0x62,
0x4D, 0x13, 0x26, 0x4C, 0x11, 0x22, 0x44, 0x01
};
/* Precomputed GF(2^7) log table: gf_log[x] = log_alpha(x) for x=0..127
* gf_log[0] is undefined (set to 0 for safety). */
static const byte gf_log[GF_SIZE] = {
0x00, 0x00, 0x01, 0x1F, 0x02, 0x3E, 0x20, 0x67,
0x03, 0x07, 0x3F, 0x0F, 0x21, 0x54, 0x68, 0x5D,
0x04, 0x7C, 0x08, 0x79, 0x40, 0x4F, 0x10, 0x73,
0x22, 0x0B, 0x55, 0x26, 0x69, 0x2E, 0x5E, 0x33,
0x05, 0x52, 0x7D, 0x3C, 0x09, 0x2C, 0x7A, 0x4D,
0x41, 0x43, 0x50, 0x2A, 0x11, 0x45, 0x74, 0x17,
0x23, 0x76, 0x0C, 0x1C, 0x56, 0x19, 0x27, 0x39,
0x6A, 0x13, 0x2F, 0x59, 0x5F, 0x47, 0x34, 0x6E,
0x06, 0x0E, 0x53, 0x5C, 0x7E, 0x1E, 0x3D, 0x66,
0x0A, 0x25, 0x2D, 0x32, 0x7B, 0x78, 0x4E, 0x72,
0x42, 0x29, 0x44, 0x16, 0x51, 0x3B, 0x2B, 0x4C,
0x12, 0x58, 0x46, 0x6D, 0x75, 0x1B, 0x18, 0x38,
0x24, 0x31, 0x77, 0x71, 0x0D, 0x5B, 0x1D, 0x65,
0x57, 0x6C, 0x1A, 0x37, 0x28, 0x15, 0x3A, 0x4B,
0x6B, 0x36, 0x14, 0x4A, 0x30, 0x70, 0x5A, 0x64,
0x60, 0x61, 0x48, 0x62, 0x35, 0x49, 0x6F, 0x63
};
/* GF multiplication */
static WC_INLINE byte gf_mul(byte a, byte b)
{
if (a == 0 || b == 0)
return 0;
return gf_exp[(gf_log[a] + gf_log[b]) % GF_MASK];
}
/* GF inverse */
static WC_INLINE byte gf_inv(byte a)
{
if (a == 0)
return 0;
return gf_exp[GF_MASK - gf_log[a]];
}
/* ---- BCH syndrome computation ---- */
/* Evaluate syndrome: S_root = c(alpha^root) where codeword bits are packed
* MSB-first. Bit at position j in the byte array corresponds to the
* coefficient of x^(N-1-j) in the codeword polynomial, so we evaluate
* using alpha^(root*(N-1-j)) to correctly compute c(alpha^root). */
static byte bch_syndrome_eval(const byte* codeword, int root)
{
byte s = 0;
int j;
for (j = 0; j < WC_PUF_BCH_N; j++) {
int byteIdx = j / 8;
int bitIdx = 7 - (j % 8);
if (codeword[byteIdx] & (1 << bitIdx)) {
/* coefficient of x^(N-1-j), evaluated at alpha^root */
s ^= gf_exp[(root * (WC_PUF_BCH_N - 1 - j)) % GF_MASK];
}
}
return s;
}
/* Compute 2t syndromes S[1..2t] */
static void bch_syndromes(const byte* codeword, byte* syndromes)
{
int i;
for (i = 1; i <= 2 * WC_PUF_BCH_T; i++) {
syndromes[i] = bch_syndrome_eval(codeword, i);
}
}
/* ---- Berlekamp-Massey algorithm ---- */
/* Find error locator polynomial sigma(x) from syndromes.
* sigma[] has degree <= t, coefficients in GF(2^7).
* Returns degree of sigma, or -1 on failure. */
static int bch_berlekamp_massey(const byte* syndromes, byte* sigma)
{
byte C[WC_PUF_BCH_T + 1]; /* current polynomial */
byte B[WC_PUF_BCH_T + 1]; /* previous polynomial */
byte T[WC_PUF_BCH_T + 1]; /* temp */
int L = 0; /* current length */
int m = 1; /* shift counter */
byte b = 1; /* previous discrepancy */
int n, i, degC;
XMEMSET(C, 0, sizeof(C));
XMEMSET(B, 0, sizeof(B));
C[0] = 1;
B[0] = 1;
for (n = 0; n < 2 * WC_PUF_BCH_T; n++) {
/* compute discrepancy d */
byte d = syndromes[n + 1];
for (i = 1; i <= L; i++) {
d ^= gf_mul(C[i], syndromes[n + 1 - i]);
}
if (d == 0) {
m++;
}
else if (2 * L <= n) {
/* update: T(x) = C(x), C(x) -= (d/b)*x^m * B(x), B=T, L=n+1-L */
byte coeff = gf_mul(d, gf_inv(b));
XMEMCPY(T, C, sizeof(T));
for (i = m; i <= WC_PUF_BCH_T; i++) {
C[i] ^= gf_mul(coeff, B[i - m]);
}
XMEMCPY(B, T, sizeof(B));
L = n + 1 - L;
b = d;
m = 1;
}
else {
/* C(x) -= (d/b)*x^m * B(x) */
byte coeff = gf_mul(d, gf_inv(b));
for (i = m; i <= WC_PUF_BCH_T; i++) {
C[i] ^= gf_mul(coeff, B[i - m]);
}
m++;
}
}
XMEMCPY(sigma, C, (WC_PUF_BCH_T + 1));
/* find degree */
degC = 0;
for (i = WC_PUF_BCH_T; i >= 0; i--) {
if (sigma[i] != 0) {
degC = i;
break;
}
}
if (degC > WC_PUF_BCH_T)
return -1;
return degC;
}
/* ---- Chien search: find error locations ---- */
/* Evaluate sigma at alpha^(-j) for j=0..126. Returns number of roots found.
* Error positions stored in errPos[] as byte-scan positions (MSB-first).
* Chien search root j maps to bit position (N-1-j) to match the MSB-first
* codeword layout used by the syndrome computation. */
static int bch_chien_search(const byte* sigma, int deg, int* errPos)
{
int count = 0;
int j;
for (j = 0; j < WC_PUF_BCH_N; j++) {
byte val = 0;
int i;
for (i = 0; i <= deg; i++) {
if (sigma[i] != 0) {
/* sigma[i] * alpha^(-i*j) */
int exp_val = (GF_MASK - ((i * j) % GF_MASK)) % GF_MASK;
val ^= gf_mul(sigma[i], gf_exp[exp_val]);
}
}
if (val == 0) {
if (count >= WC_PUF_BCH_T)
return -1; /* too many roots, protect errPos[] bounds */
errPos[count] = WC_PUF_BCH_N - 1 - j;
count++;
}
}
return count;
}
/* ---- BCH encode: compute parity for 64-bit message ---- */
/* Generator polynomial for BCH(127,64,t=10) over GF(2).
* This is the product of minimal polynomials of alpha^1..alpha^(2t).
* Degree = n - k = 63. Stored as 64-bit value (coefficients mod 2).
* g(x) = GCD of min polys of consecutive roots. Precomputed. */
/* We store g(x) as 8 bytes, MSB first, degree-63 coefficient in bit 63.
* The leading coefficient (x^63) is implicit. */
static const byte bch_genpoly[8] = {
0x21, 0xAB, 0x81, 0x5B, 0xC7, 0xEC, 0x80, 0x25
};
/* Encode 64-bit message into 127-bit codeword.
* msg: 8 bytes (64 bits), output: 16 bytes (127 bits, MSB aligned).
* Systematic encoding: codeword = [msg(64) | parity(63)]. */
static void bch_encode(const byte* msg, byte* codeword)
{
byte shift_reg[8]; /* 63-bit shift register for parity */
int i, j;
XMEMSET(shift_reg, 0, sizeof(shift_reg));
/* Process each of the 64 message bits */
for (i = 0; i < WC_PUF_BCH_K; i++) {
int byteIdx = i / 8;
int bitIdx = 7 - (i % 8);
byte msgBit = (msg[byteIdx] >> bitIdx) & 1;
/* feedback = msgBit XOR MSB of shift register */
byte fb = msgBit ^ ((shift_reg[0] >> 6) & 1);
/* shift register left by 1 */
for (j = 0; j < 7; j++) {
shift_reg[j] = (byte)((shift_reg[j] << 1) |
(shift_reg[j + 1] >> 7));
}
shift_reg[7] = (byte)(shift_reg[7] << 1);
/* keep the register at exactly 63 bits - bit 7 of byte 0 is unused */
shift_reg[0] &= 0x7F;
/* XOR with generator if feedback is 1 */
if (fb) {
for (j = 0; j < 8; j++) {
shift_reg[j] ^= bch_genpoly[j];
}
/* generator polynomial bit 7 is 0; mask defensively in case it
* ever changes so the unused slot can never affect parity */
shift_reg[0] &= 0x7F;
}
}
/* Build codeword: [msg(64 bits) | parity(63 bits)] = 127 bits */
XMEMSET(codeword, 0, 16);
XMEMCPY(codeword, msg, 8); /* message in first 64 bits */
/* parity: bits 64..126 from shift_reg bits 0..62 */
/* shift_reg holds 63 bits in bits [6..0] of byte 0, then bytes 1..7 */
/* We need to place these starting at bit position 64 in codeword */
for (i = 0; i < 63; i++) {
int srcByte;
int srcBit;
/* shift_reg MSB is bit 6 of byte 0 */
if (i < 7) {
srcByte = 0;
srcBit = 6 - i;
}
else {
srcByte = (i - 7) / 8 + 1;
srcBit = 7 - ((i - 7) % 8);
}
if (shift_reg[srcByte] & (1 << srcBit)) {
int dstPos = 64 + i;
int dstByte = dstPos / 8;
int dstBit = 7 - (dstPos % 8);
codeword[dstByte] |= (byte)(1 << dstBit);
}
}
}
/* ---- BCH decode ---- */
/* Decode 127-bit codeword, correct up to t=10 errors.
* Extracts 64-bit message into msg (8 bytes).
* Returns 0 on success, negative on uncorrectable error. */
static int bch_decode(byte* codeword, byte* msg)
{
byte syndr[2 * WC_PUF_BCH_T + 1];
byte sigma[WC_PUF_BCH_T + 1];
int errPos[WC_PUF_BCH_T];
int deg, numErr;
int i;
int allZero = 1;
bch_syndromes(codeword, syndr);
/* check if all syndromes are zero (no errors) */
for (i = 1; i <= 2 * WC_PUF_BCH_T; i++) {
if (syndr[i] != 0) {
allZero = 0;
break;
}
}
if (allZero) {
/* no errors, extract message directly */
XMEMCPY(msg, codeword, 8);
return 0;
}
deg = bch_berlekamp_massey(syndr, sigma);
if (deg < 0)
return PUF_RECONSTRUCT_E;
numErr = bch_chien_search(sigma, deg, errPos);
if (numErr != deg)
return PUF_RECONSTRUCT_E; /* number of roots must match degree */
/* correct errors by flipping bits */
for (i = 0; i < numErr; i++) {
int pos = errPos[i];
if (pos < WC_PUF_BCH_N) {
int byteIdx = pos / 8;
int bitIdx = 7 - (pos % 8);
codeword[byteIdx] ^= (byte)(1 << bitIdx);
}
}
/* verify the correction actually fixed the codeword by recomputing
* syndromes - guards against silent miscorrection when the input has
* more than t errors and the decoder is led to a different valid
* codeword (which would otherwise produce a wrong key/identity) */
bch_syndromes(codeword, syndr);
for (i = 1; i <= 2 * WC_PUF_BCH_T; i++) {
if (syndr[i] != 0)
return PUF_RECONSTRUCT_E;
}
/* extract message (first 64 bits) */
XMEMCPY(msg, codeword, 8);
return 0;
}
/* ========================================================================== */
/* PUF API */
/* ========================================================================== */
/* Get a single bit from byte array (MSB-first bit ordering) */
static WC_INLINE byte getBit(const byte* data, int bitPos)
{
return (data[bitPos / 8] >> (7 - (bitPos % 8))) & 1;
}
/* Set a single bit in byte array (MSB-first bit ordering) */
static WC_INLINE void setBit(byte* data, int bitPos, byte val)
{
int byteIdx = bitPos / 8;
int bitIdx = 7 - (bitPos % 8);
if (val)
data[byteIdx] |= (byte)(1 << bitIdx);
else
data[byteIdx] &= (byte)~(1 << bitIdx);
}
/* Extract 127 bits from raw SRAM starting at given bit offset */
static void extractCodeword(const byte* sram, int bitOffset, byte* cw)
{
int i;
XMEMSET(cw, 0, 16);
for (i = 0; i < WC_PUF_BCH_N; i++) {
setBit(cw, i, getBit(sram, bitOffset + i));
}
}
/* Store 127 bits into helper data at given bit offset */
static void storeCodeword(byte* helper, int bitOffset, const byte* cw)
{
int i;
for (i = 0; i < WC_PUF_BCH_N; i++) {
setBit(helper, bitOffset + i, getBit(cw, i));
}
}
int wc_PufInit(wc_PufCtx* ctx)
{
WOLFSSL_ENTER("wc_PufInit");
if (ctx == NULL)
return BAD_FUNC_ARG;
XMEMSET(ctx, 0, sizeof(wc_PufCtx));
return 0;
}
int wc_PufReadSram(wc_PufCtx* ctx, const byte* sramAddr, word32 sramSz)
{
WOLFSSL_ENTER("wc_PufReadSram");
if (ctx == NULL || sramAddr == NULL)
return BAD_FUNC_ARG;
if (sramSz < WC_PUF_RAW_BYTES)
return PUF_READ_E;
#ifdef WOLFSSL_PUF_TEST
if (ctx->testDataSet) {
/* rawSram already populated by wc_PufSetTestData */
ctx->flags |= WC_PUF_FLAG_SRAM_SET;
return 0;
}
#endif
XMEMCPY(ctx->rawSram, sramAddr, WC_PUF_RAW_BYTES);
ctx->flags |= WC_PUF_FLAG_SRAM_SET;
return 0;
}
int wc_PufEnroll(wc_PufCtx* ctx)
{
int i, ret;
byte msg[8]; /* 64-bit message */
byte cw[16]; /* 127-bit codeword */
byte rawCw[16];
byte helperCw[16];
WOLFSSL_ENTER("wc_PufEnroll");
if (ctx == NULL)
return BAD_FUNC_ARG;
if (!(ctx->flags & WC_PUF_FLAG_SRAM_SET))
return PUF_ENROLL_E;
XMEMSET(ctx->helperData, 0, WC_PUF_HELPER_BYTES);
XMEMSET(ctx->stableBits, 0, WC_PUF_STABLE_BYTES);
for (i = 0; i < WC_PUF_NUM_CODEWORDS; i++) {
/* extract 64 message bits from raw SRAM */
int bitOff = i * 128; /* 128-bit stride for alignment */
int j;
XMEMSET(msg, 0, sizeof(msg));
for (j = 0; j < WC_PUF_BCH_K; j++) {
setBit(msg, j, getBit(ctx->rawSram, bitOff + j));
}
/* save stable bits */
XMEMCPY(ctx->stableBits + i * 8, msg, 8);
/* encode message into BCH codeword */
bch_encode(msg, cw);
/* helper = raw XOR codeword (mask) */
extractCodeword(ctx->rawSram, bitOff, rawCw);
XMEMSET(helperCw, 0, 16);
for (j = 0; j < 16; j++) {
helperCw[j] = rawCw[j] ^ cw[j];
}
storeCodeword(ctx->helperData, i * WC_PUF_BCH_N, helperCw);
}
/* compute identity = SHA-256(stableBits) */
ret = wc_PufHashDirect(ctx->stableBits, WC_PUF_STABLE_BYTES, ctx->identity);
/* zeroize sensitive stack buffers */
ForceZero(msg, sizeof(msg));
ForceZero(cw, sizeof(cw));
ForceZero(rawCw, sizeof(rawCw));
ForceZero(helperCw, sizeof(helperCw));
if (ret != 0)
return PUF_ENROLL_E;
ctx->flags |= WC_PUF_FLAG_ENROLLED | WC_PUF_FLAG_READY;
return 0;
}
int wc_PufReconstruct(wc_PufCtx* ctx, const byte* helperData, word32 helperSz)
{
int i, ret;
byte rawCw[16];
byte helperCw[16];
byte noisyCw[16];
byte msg[8];
WOLFSSL_ENTER("wc_PufReconstruct");
if (ctx == NULL || helperData == NULL)
return BAD_FUNC_ARG;
if (helperSz < WC_PUF_HELPER_BYTES)
return PUF_RECONSTRUCT_E;
if (!(ctx->flags & WC_PUF_FLAG_SRAM_SET))
return PUF_RECONSTRUCT_E;
XMEMSET(ctx->stableBits, 0, WC_PUF_STABLE_BYTES);
for (i = 0; i < WC_PUF_NUM_CODEWORDS; i++) {
int bitOff = i * 128;
int j;
/* get raw SRAM bits for this codeword */
extractCodeword(ctx->rawSram, bitOff, rawCw);
/* get helper data for this codeword */
XMEMSET(helperCw, 0, 16);
for (j = 0; j < WC_PUF_BCH_N; j++) {
setBit(helperCw, j, getBit(helperData, i * WC_PUF_BCH_N + j));
}
/* noisy codeword = raw XOR helper */
for (j = 0; j < 16; j++) {
noisyCw[j] = rawCw[j] ^ helperCw[j];
}
/* BCH decode to recover original message */
ret = bch_decode(noisyCw, msg);
if (ret != 0) {
ForceZero(rawCw, sizeof(rawCw));
ForceZero(helperCw, sizeof(helperCw));
ForceZero(noisyCw, sizeof(noisyCw));
ForceZero(msg, sizeof(msg));
ForceZero(ctx->stableBits, WC_PUF_STABLE_BYTES);
ctx->flags &= (word32)~WC_PUF_FLAG_READY;
return PUF_RECONSTRUCT_E;
}
XMEMCPY(ctx->stableBits + i * 8, msg, 8);
}
/* compute identity */
ret = wc_PufHashDirect(ctx->stableBits, WC_PUF_STABLE_BYTES, ctx->identity);
/* zeroize sensitive stack buffers */
ForceZero(rawCw, sizeof(rawCw));
ForceZero(helperCw, sizeof(helperCw));
ForceZero(noisyCw, sizeof(noisyCw));
ForceZero(msg, sizeof(msg));
if (ret != 0)
return PUF_RECONSTRUCT_E;
ctx->flags |= WC_PUF_FLAG_READY;
return 0;
}
int wc_PufDeriveKey(wc_PufCtx* ctx, const byte* info, word32 infoSz,
byte* key, word32 keySz)
{
WOLFSSL_ENTER("wc_PufDeriveKey");
if (ctx == NULL || key == NULL)
return BAD_FUNC_ARG;
if (!(ctx->flags & WC_PUF_FLAG_READY))
return PUF_DERIVE_KEY_E;
if (keySz == 0)
return BAD_FUNC_ARG;
/* Documented contract: info may be NULL. Normalize so callers can pass
* (NULL, anything) without forwarding an invalid pointer/length pair to
* HKDF. */
if (info == NULL)
infoSz = 0;
#ifdef HAVE_HKDF
{
/* HKDF with stable bits as IKM, identity as salt */
int ret;
ret = wc_HKDF(WC_PUF_HASH_TYPE,
ctx->stableBits, WC_PUF_STABLE_BYTES,
ctx->identity, WC_PUF_ID_SZ,
info, infoSz,
key, keySz);
if (ret != 0)
return PUF_DERIVE_KEY_E;
return 0;
}
#else
(void)info;
(void)infoSz;
return PUF_DERIVE_KEY_E;
#endif
}
int wc_PufGetIdentity(wc_PufCtx* ctx, byte* id, word32 idSz)
{
WOLFSSL_ENTER("wc_PufGetIdentity");
if (ctx == NULL || id == NULL)
return BAD_FUNC_ARG;
if (!(ctx->flags & WC_PUF_FLAG_READY))
return PUF_IDENTITY_E;
if (idSz < WC_PUF_ID_SZ)
return PUF_IDENTITY_E;
XMEMCPY(id, ctx->identity, WC_PUF_ID_SZ);
return 0;
}
int wc_PufZeroize(wc_PufCtx* ctx)
{
WOLFSSL_ENTER("wc_PufZeroize");
if (ctx == NULL)
return BAD_FUNC_ARG;
ForceZero(ctx, sizeof(wc_PufCtx));
return 0;
}
#ifdef WOLFSSL_PUF_TEST
int wc_PufSetTestData(wc_PufCtx* ctx, const byte* data, word32 sz)
{
WOLFSSL_ENTER("wc_PufSetTestData");
if (ctx == NULL || data == NULL)
return BAD_FUNC_ARG;
if (sz < WC_PUF_RAW_BYTES)
return PUF_READ_E;
/* Copy test data directly into rawSram and set flag */
XMEMCPY(ctx->rawSram, data, WC_PUF_RAW_BYTES);
ctx->testDataSet = 1;
ctx->flags |= WC_PUF_FLAG_SRAM_SET;
return 0;
}
#endif /* WOLFSSL_PUF_TEST */
#endif /* WOLFSSL_PUF */
+235
View File
@@ -437,6 +437,9 @@ static const byte const_byte_array[] = "A+Gd\0\0\0";
#ifdef WOLFSSL_SM4
#include <wolfssl/wolfcrypt/sm4.h>
#endif
#ifdef WOLFSSL_PUF
#include <wolfssl/wolfcrypt/puf.h>
#endif
#ifdef HAVE_LIBZ
#include <wolfssl/wolfcrypt/compress.h>
#endif
@@ -889,6 +892,9 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t camellia_test(void);
#ifdef WOLFSSL_SM4
WOLFSSL_TEST_SUBROUTINE wc_test_ret_t sm4_test(void);
#endif
#ifdef WOLFSSL_PUF
WOLFSSL_TEST_SUBROUTINE wc_test_ret_t puf_test(void);
#endif
#ifdef WC_RSA_NO_PADDING
WOLFSSL_TEST_SUBROUTINE wc_test_ret_t rsa_no_pad_test(void);
#endif
@@ -2940,6 +2946,13 @@ options: [-s max_relative_stack_bytes] [-m max_relative_heap_memory_bytes]\n\
TEST_PASS("SM-4 test passed!\n");
#endif
#ifdef WOLFSSL_PUF
if ( (ret = puf_test()) != 0)
return err_sys("PUF test failed!\n", ret);
else
TEST_PASS("PUF test passed!\n");
#endif
#if !defined(NO_RSA) && !defined(HAVE_RENESAS_SYNC)
#ifdef WC_RSA_NO_PADDING
if ( (ret = rsa_no_pad_test()) != 0)
@@ -20537,6 +20550,228 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t sm4_test(void)
}
#endif
#ifdef WOLFSSL_PUF
WOLFSSL_TEST_SUBROUTINE wc_test_ret_t puf_test(void)
{
#if defined(WOLFSSL_PUF_TEST) && defined(HAVE_HKDF) && \
(!defined(NO_SHA256) || defined(WOLFSSL_SHA3))
wc_test_ret_t ret = 0;
wc_PufCtx ctx;
byte key1[WC_PUF_KEY_SZ];
byte key2[WC_PUF_KEY_SZ];
byte id1[WC_PUF_ID_SZ];
byte id2[WC_PUF_ID_SZ];
/* deterministic test SRAM pattern: 256 bytes */
WOLFSSL_SMALL_STACK_STATIC const byte testSram[WC_PUF_RAW_BYTES] = {
0xA5, 0x3C, 0x7E, 0x19, 0xF0, 0x82, 0x4D, 0xBB,
0x6A, 0xC1, 0x55, 0x93, 0xE7, 0x2F, 0xD8, 0x04,
0x91, 0x68, 0xAE, 0x3B, 0xFC, 0xD7, 0x42, 0x0E,
0x85, 0x5A, 0xC9, 0x76, 0x1D, 0xB3, 0xEF, 0x60,
0x4C, 0x87, 0xDA, 0x25, 0xF1, 0x6E, 0x09, 0xB2,
0x73, 0xAC, 0x58, 0xE4, 0x3F, 0x96, 0xCB, 0x17,
0x8D, 0x62, 0xA0, 0x4E, 0xFB, 0xD5, 0x31, 0x79,
0xC6, 0x14, 0xBE, 0x8A, 0x47, 0xF3, 0x2D, 0x98,
0x5B, 0xE6, 0x0C, 0xA7, 0x64, 0xDF, 0x39, 0x80,
0xB5, 0x52, 0xCD, 0x18, 0x7B, 0xE1, 0x46, 0x9F,
0x23, 0xAA, 0x6D, 0xD0, 0x84, 0xF7, 0x3E, 0xB9,
0x51, 0xC2, 0x0F, 0x75, 0xEC, 0x48, 0x97, 0x2A,
0xDE, 0x63, 0xBC, 0x10, 0x86, 0xF9, 0x43, 0xAD,
0x5E, 0xC8, 0x27, 0x94, 0x6B, 0xD1, 0x3A, 0xB0,
0x7C, 0xE5, 0x08, 0xA1, 0x56, 0xCF, 0x4A, 0x8E,
0x35, 0xFD, 0x61, 0xB7, 0x22, 0x99, 0xD4, 0x1C,
0x70, 0xEE, 0x4B, 0x83, 0x2E, 0xA6, 0x5D, 0xF4,
0x36, 0xBD, 0x69, 0xC0, 0x15, 0x9B, 0xE8, 0x41,
0x8C, 0x53, 0xAB, 0x07, 0x74, 0xDC, 0x28, 0x95,
0x6F, 0xD3, 0x3D, 0xBA, 0x50, 0xC4, 0x1E, 0x89,
0xF6, 0x44, 0xAE, 0x5F, 0xC7, 0x12, 0x9A, 0xE3,
0x37, 0xB1, 0x66, 0xDB, 0x29, 0x8B, 0x54, 0xA2,
0x0D, 0x78, 0xED, 0x40, 0x93, 0x2C, 0xBF, 0x67,
0xD6, 0x3C, 0xA9, 0x57, 0xCE, 0x1A, 0x81, 0xF5,
0x49, 0x9E, 0x24, 0xB8, 0x6C, 0xD2, 0x38, 0xA4,
0x5C, 0xE9, 0x01, 0x7A, 0xDD, 0x45, 0x90, 0x2B,
0xBB, 0x62, 0xC3, 0x16, 0x8F, 0xF8, 0x4E, 0xA3,
0x34, 0xB6, 0x6E, 0xD9, 0x20, 0x9C, 0x59, 0xE2,
0x0B, 0x77, 0xEA, 0x42, 0x8D, 0x33, 0xCA, 0x5B,
0xFE, 0x11, 0x7F, 0xA8, 0x46, 0xD4, 0x2F, 0x96,
0x65, 0xBC, 0x03, 0x9D, 0xE0, 0x58, 0xAF, 0x71,
0xC5, 0x1B, 0x87, 0xFA, 0x4D, 0xB4, 0x26, 0xDF
};
/* noisy SRAM: same as testSram but with a few flipped bits */
byte noisySram[WC_PUF_RAW_BYTES];
byte helperBuf[WC_PUF_HELPER_BYTES];
const byte info[] = "puf-test-context";
WOLFSSL_ENTER("puf_test");
/* ---- Test 1: Init ---- */
ret = wc_PufInit(&ctx);
if (ret != 0)
return WC_TEST_RET_ENC_EC(ret);
/* ---- Test 2: SetTestData + Enroll ---- */
ret = wc_PufSetTestData(&ctx, testSram, sizeof(testSram));
if (ret != 0)
return WC_TEST_RET_ENC_EC(ret);
ret = wc_PufEnroll(&ctx);
if (ret != 0)
return WC_TEST_RET_ENC_EC(ret);
/* save helper data and identity */
XMEMCPY(helperBuf, ctx.helperData, WC_PUF_HELPER_BYTES);
ret = wc_PufGetIdentity(&ctx, id1, sizeof(id1));
if (ret != 0)
return WC_TEST_RET_ENC_EC(ret);
/* derive a key */
ret = wc_PufDeriveKey(&ctx, info, sizeof(info), key1, sizeof(key1));
if (ret != 0)
return WC_TEST_RET_ENC_EC(ret);
/* ---- Test 3: Reconstruct with same data (no noise) ---- */
ret = wc_PufInit(&ctx);
if (ret != 0)
return WC_TEST_RET_ENC_EC(ret);
ret = wc_PufSetTestData(&ctx, testSram, sizeof(testSram));
if (ret != 0)
return WC_TEST_RET_ENC_EC(ret);
/* need to call ReadSram to populate rawSram (test data already set) */
ret = wc_PufReadSram(&ctx, testSram, sizeof(testSram));
if (ret != 0)
return WC_TEST_RET_ENC_EC(ret);
ret = wc_PufReconstruct(&ctx, helperBuf, WC_PUF_HELPER_BYTES);
if (ret != 0)
return WC_TEST_RET_ENC_EC(ret);
ret = wc_PufGetIdentity(&ctx, id2, sizeof(id2));
if (ret != 0)
return WC_TEST_RET_ENC_EC(ret);
/* identity must match */
if (XMEMCMP(id1, id2, WC_PUF_ID_SZ) != 0)
return WC_TEST_RET_ENC_NC;
/* derive key again - must match */
ret = wc_PufDeriveKey(&ctx, info, sizeof(info), key2, sizeof(key2));
if (ret != 0)
return WC_TEST_RET_ENC_EC(ret);
if (XMEMCMP(key1, key2, WC_PUF_KEY_SZ) != 0)
return WC_TEST_RET_ENC_NC;
/* ---- Test 4: Reconstruct with noisy data (few bit flips) ---- */
XMEMCPY(noisySram, testSram, sizeof(testSram));
/* flip a few bits in each 128-bit block (within BCH correction limit) */
noisySram[0] ^= 0x01; /* block 0: 1 bit flip */
noisySram[16] ^= 0x03; /* block 1: 2 bit flips */
noisySram[32] ^= 0x05; /* block 2: 2 bit flips */
noisySram[48] ^= 0x11; /* block 3: 2 bit flips */
noisySram[64] ^= 0x80; /* block 4: 1 bit flip */
ret = wc_PufInit(&ctx);
if (ret != 0)
return WC_TEST_RET_ENC_EC(ret);
ret = wc_PufSetTestData(&ctx, noisySram, sizeof(noisySram));
if (ret != 0)
return WC_TEST_RET_ENC_EC(ret);
ret = wc_PufReadSram(&ctx, noisySram, sizeof(noisySram));
if (ret != 0)
return WC_TEST_RET_ENC_EC(ret);
ret = wc_PufReconstruct(&ctx, helperBuf, WC_PUF_HELPER_BYTES);
if (ret != 0)
return WC_TEST_RET_ENC_EC(ret);
/* identity should still match after error correction */
ret = wc_PufGetIdentity(&ctx, id2, sizeof(id2));
if (ret != 0)
return WC_TEST_RET_ENC_EC(ret);
if (XMEMCMP(id1, id2, WC_PUF_ID_SZ) != 0)
return WC_TEST_RET_ENC_NC;
/* derived key should still match */
ret = wc_PufDeriveKey(&ctx, info, sizeof(info), key2, sizeof(key2));
if (ret != 0)
return WC_TEST_RET_ENC_EC(ret);
if (XMEMCMP(key1, key2, WC_PUF_KEY_SZ) != 0)
return WC_TEST_RET_ENC_NC;
/* ---- Test 4b: Reconstruct with too many bit errors (should fail) ---- */
{
byte tooNoisySram[WC_PUF_RAW_BYTES];
XMEMCPY(tooNoisySram, testSram, sizeof(testSram));
/* flip 12 bits in block 0 (exceeds t=10 correction limit) */
tooNoisySram[0] ^= 0xFF; /* 8 flips */
tooNoisySram[1] ^= 0x0F; /* 4 flips = 12 total > t=10 */
ret = wc_PufInit(&ctx);
if (ret != 0)
return WC_TEST_RET_ENC_EC(ret);
ret = wc_PufSetTestData(&ctx, tooNoisySram, sizeof(tooNoisySram));
if (ret != 0)
return WC_TEST_RET_ENC_EC(ret);
ret = wc_PufReadSram(&ctx, tooNoisySram, sizeof(tooNoisySram));
if (ret != 0)
return WC_TEST_RET_ENC_EC(ret);
if (wc_PufReconstruct(&ctx, helperBuf, WC_PUF_HELPER_BYTES)
!= WC_NO_ERR_TRACE(PUF_RECONSTRUCT_E))
return WC_TEST_RET_ENC_NC;
}
/* ---- Test 5: Bad argument checks ---- */
if (wc_PufInit(NULL) != WC_NO_ERR_TRACE(BAD_FUNC_ARG))
return WC_TEST_RET_ENC_NC;
if (wc_PufReadSram(NULL, testSram, sizeof(testSram))
!= WC_NO_ERR_TRACE(BAD_FUNC_ARG))
return WC_TEST_RET_ENC_NC;
if (wc_PufDeriveKey(&ctx, info, sizeof(info), NULL, 32)
!= WC_NO_ERR_TRACE(BAD_FUNC_ARG))
return WC_TEST_RET_ENC_NC;
if (wc_PufEnroll(NULL) != WC_NO_ERR_TRACE(BAD_FUNC_ARG))
return WC_TEST_RET_ENC_NC;
if (wc_PufReconstruct(NULL, helperBuf, WC_PUF_HELPER_BYTES)
!= WC_NO_ERR_TRACE(BAD_FUNC_ARG))
return WC_TEST_RET_ENC_NC;
if (wc_PufZeroize(NULL) != WC_NO_ERR_TRACE(BAD_FUNC_ARG))
return WC_TEST_RET_ENC_NC;
/* too-small identity buffer */
if (wc_PufGetIdentity(&ctx, id1, WC_PUF_ID_SZ - 1)
!= WC_NO_ERR_TRACE(PUF_IDENTITY_E))
return WC_TEST_RET_ENC_NC;
/* ---- Test 6: Zeroize ---- */
ret = wc_PufZeroize(&ctx);
if (ret != 0)
return WC_TEST_RET_ENC_EC(ret);
/* after zeroize, derive should fail (not ready) */
if (wc_PufDeriveKey(&ctx, info, sizeof(info), key1, sizeof(key1))
!= WC_NO_ERR_TRACE(PUF_DERIVE_KEY_E))
return WC_TEST_RET_ENC_NC;
return 0;
#else
return 0;
#endif /* WOLFSSL_PUF_TEST && HAVE_HKDF && (!NO_SHA256 || WOLFSSL_SHA3) */
}
#endif /* WOLFSSL_PUF */
#ifdef HAVE_XCHACHA
WOLFSSL_TEST_SUBROUTINE wc_test_ret_t XChaCha_test(void) {
+10 -2
View File
@@ -314,8 +314,16 @@ enum wolfCrypt_ErrorCodes {
ALREADY_E = -1007, /* Operation was redundant or preempted */
SEQ_OVERFLOW_E = -1008, /* Sequence counter would overflow */
WC_SPAN2_LAST_E = -1008, /* Update to indicate last used error code */
WC_LAST_E = -1008, /* the last code used either here or in
PUF_INIT_E = -1009, /* PUF initialization failed (reserved) */
PUF_READ_E = -1010, /* PUF SRAM read failed */
PUF_ENROLL_E = -1011, /* PUF enrollment failed */
PUF_RECONSTRUCT_E = -1012, /* PUF reconstruction failed */
PUF_DERIVE_KEY_E = -1013, /* PUF key derivation failed */
PUF_IDENTITY_E = -1014, /* PUF identity retrieval failed */
WC_SPAN2_LAST_E = -1014, /* Update to indicate last used error code */
WC_LAST_E = -1014, /* the last code used either here or in
* error-ssl.h */
WC_SPAN2_MIN_CODE_E = -1999, /* Last usable code in span 2 */
+1
View File
@@ -90,6 +90,7 @@ nobase_include_HEADERS+= \
wolfssl/wolfcrypt/wc_xmss.h \
wolfssl/wolfcrypt/ext_xmss.h \
wolfssl/wolfcrypt/wc_slhdsa.h \
wolfssl/wolfcrypt/puf.h \
wolfssl/wolfcrypt/oid_sum.h
noinst_HEADERS+= \
+116
View File
@@ -0,0 +1,116 @@
/* puf.h
*
* 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
*/
/*!
\file wolfssl/wolfcrypt/puf.h
\brief SRAM PUF (Physically Unclonable Function) support for 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.
Build: ./configure --enable-puf (auto-enables HKDF)
For a bare-metal example (tested on NUCLEO-H563ZI), see:
https://github.com/wolfSSL/wolfssl-examples/tree/master/puf
*/
#ifndef WOLF_CRYPT_PUF_H
#define WOLF_CRYPT_PUF_H
#include <wolfssl/wolfcrypt/settings.h>
#ifdef WOLFSSL_PUF
/* PUF is not a FIPS-validated algorithm. Fail loudly at compile time rather
* than producing undefined references at link time when WOLFSSL_PUF is
* combined with HAVE_FIPS. */
#if defined(HAVE_FIPS)
#error "WOLFSSL_PUF is not available when HAVE_FIPS is defined"
#endif
#include <wolfssl/wolfcrypt/types.h>
#ifdef __cplusplus
extern "C" {
#endif
/* BCH(127,64,t=10) parameters */
#define WC_PUF_BCH_M 7 /* GF(2^7) */
#define WC_PUF_BCH_N 127 /* codeword length */
#define WC_PUF_BCH_K 64 /* message length */
#define WC_PUF_BCH_T 10 /* error correction capability */
/* PUF dimensions */
#define WC_PUF_NUM_CODEWORDS 16 /* 16 codewords */
#define WC_PUF_RAW_BITS 2048 /* 16 x 128 bits (rounded up for storage) */
#define WC_PUF_RAW_BYTES (WC_PUF_RAW_BITS / 8) /* 256 bytes */
#define WC_PUF_STABLE_BITS 1024 /* 16 x 64 message bits */
#define WC_PUF_STABLE_BYTES (WC_PUF_STABLE_BITS / 8) /* 128 bytes */
/* Helper data: 16 codewords x 127 bits, packed into bytes */
#define WC_PUF_HELPER_BITS (WC_PUF_NUM_CODEWORDS * WC_PUF_BCH_N)
#define WC_PUF_HELPER_BYTES ((WC_PUF_HELPER_BITS + 7) / 8) /* 254 bytes */
/* Output key size */
#define WC_PUF_KEY_SZ 32 /* 256-bit derived key */
/* Identity hash size (SHA-256 or SHA3-256 with WC_PUF_SHA3) */
#define WC_PUF_ID_SZ 32
/* Flags for wc_PufCtx.flags */
#define WC_PUF_FLAG_ENROLLED 0x01
#define WC_PUF_FLAG_READY 0x02
#define WC_PUF_FLAG_SRAM_SET 0x04
typedef struct wc_PufCtx {
byte rawSram[WC_PUF_RAW_BYTES]; /* raw SRAM readout */
byte helperData[WC_PUF_HELPER_BYTES]; /* enrollment helper data */
byte stableBits[WC_PUF_STABLE_BYTES]; /* reconstructed stable bits */
byte identity[WC_PUF_ID_SZ]; /* device identity hash */
word32 flags;
#ifdef WOLFSSL_PUF_TEST
word32 testDataSet; /* flag: test data was injected */
#endif
} wc_PufCtx;
WOLFSSL_API int wc_PufInit(wc_PufCtx* ctx);
WOLFSSL_API int wc_PufReadSram(wc_PufCtx* ctx, const byte* sramAddr,
word32 sramSz);
WOLFSSL_API int wc_PufEnroll(wc_PufCtx* ctx);
WOLFSSL_API int wc_PufReconstruct(wc_PufCtx* ctx, const byte* helperData,
word32 helperSz);
WOLFSSL_API int wc_PufDeriveKey(wc_PufCtx* ctx, const byte* info, word32 infoSz,
byte* key, word32 keySz);
WOLFSSL_API int wc_PufGetIdentity(wc_PufCtx* ctx, byte* id, word32 idSz);
WOLFSSL_API int wc_PufZeroize(wc_PufCtx* ctx);
#ifdef WOLFSSL_PUF_TEST
WOLFSSL_API int wc_PufSetTestData(wc_PufCtx* ctx, const byte* data, word32 sz);
#endif
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* WOLFSSL_PUF */
#endif /* WOLF_CRYPT_PUF_H */