mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-07-05 17:00:49 +02:00
Merge pull request #10066 from dgarske/wc_puf
wolfCrypt SRAM PUF Support
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 */
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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+= \
|
||||
|
||||
@@ -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 */
|
||||
Reference in New Issue
Block a user