mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-10-24 04:09:07 +03:00
wolfCrypt SRAM PUF: configurable error correction and footprint
This commit is contained in:
committed by
Daniele Lacamera
parent
e71b086fc8
commit
14a72c96e4
53
.github/workflows/puf.yml
vendored
53
.github/workflows/puf.yml
vendored
@@ -18,18 +18,40 @@ permissions:
|
||||
|
||||
jobs:
|
||||
puf_host_test:
|
||||
name: PUF host test
|
||||
name: PUF host test (${{ matrix.config }})
|
||||
if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }}
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 6
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Cover every shipped BCH profile (t=7/10/13/15) and a non-default
|
||||
# codeword count so the profile-specific genpoly, parity register and
|
||||
# bit-packed paths are all built and exercised, not just t=10.
|
||||
config:
|
||||
- "--enable-puf --enable-puf-test"
|
||||
- "--enable-puf=small --enable-puf-test"
|
||||
- "--enable-puf=strong --enable-puf-test"
|
||||
- "--enable-puf=strongest --enable-puf-test"
|
||||
- "--enable-puf --enable-puf-test CPPFLAGS=-DWC_PUF_NUM_CODEWORDS=32"
|
||||
# Odd codeword count: every other entry has k*codewords a multiple
|
||||
# of 8, so the byte-rounded tails of stableBits/helperData carry no
|
||||
# padding and the copyBits trailing-bit branch never runs. 3 x k=78
|
||||
# (234 bits) and 3 x n=127 (381 bits) both leave pad bits.
|
||||
- "--enable-puf=small --enable-puf-test CPPFLAGS=-DWC_PUF_NUM_CODEWORDS=3"
|
||||
# Opt-in compact helper layout (parity bits only).
|
||||
- "--enable-puf=strong --enable-puf-test CPPFLAGS=-DWC_PUF_HELPER_COMPACT"
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
name: Checkout wolfSSL
|
||||
|
||||
- name: Validate BCH generator polynomials against puf.c
|
||||
run: python3 scripts/puf_bch_genpoly.py --check
|
||||
|
||||
- name: Build and test PUF
|
||||
run: |
|
||||
./autogen.sh
|
||||
./configure --enable-puf --enable-puf-test
|
||||
./configure ${{ matrix.config }}
|
||||
make
|
||||
./wolfcrypt/test/testwolfcrypt
|
||||
|
||||
@@ -39,3 +61,30 @@ jobs:
|
||||
if [ -f test-suite.log ] ; then
|
||||
cat test-suite.log
|
||||
fi
|
||||
|
||||
puf_cmake_options:
|
||||
name: PUF CMake options.h propagation
|
||||
if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }}
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 6
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
name: Checkout wolfSSL
|
||||
|
||||
# wc_PufCtx is sized from these macros, so if a profile fails to reach
|
||||
# the generated options.h the application and library disagree on
|
||||
# sizeof(wc_PufCtx). Assert they are actually emitted.
|
||||
- name: Configure with a non-default profile
|
||||
run: |
|
||||
cmake -S . -B build_puf -DWOLFSSL_PUF=yes \
|
||||
-DWOLFSSL_PUF_PROFILE=strong \
|
||||
-DWOLFSSL_PUF_NUM_CODEWORDS=32
|
||||
|
||||
- name: Assert profile reached options.h
|
||||
run: |
|
||||
grep -q '^#define WC_PUF_BCH_T 13$' build_puf/wolfssl/options.h
|
||||
grep -q '^#define WC_PUF_NUM_CODEWORDS 32$' build_puf/wolfssl/options.h
|
||||
grep -q '^#define WOLFSSL_PUF$' build_puf/wolfssl/options.h
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build_puf -j
|
||||
|
||||
@@ -770,6 +770,7 @@ WC_NO_VERBOSE_RNG
|
||||
WC_PKCS11_FIND_WITH_ID_ONLY
|
||||
WC_PKCS12_PBKDF_USING_MP_API
|
||||
WC_PROTECT_ENCRYPTED_MEM
|
||||
WC_PUF_HELPER_COMPACT
|
||||
WC_PUF_SHA3
|
||||
WC_RNG_BANK_NO_DEFAULT_SUPPORT
|
||||
WC_RNG_BLOCKING
|
||||
|
||||
@@ -2326,10 +2326,47 @@ add_option("WOLFSSL_PUF"
|
||||
"no" "yes;no")
|
||||
|
||||
if(WOLFSSL_PUF)
|
||||
# PUF BCH error-correction profile (only surfaced when PUF is enabled).
|
||||
# Other t values and WC_PUF_NUM_CODEWORDS can still be set via CFLAGS.
|
||||
add_option("WOLFSSL_PUF_PROFILE"
|
||||
"PUF BCH profile: balanced (t=10), small (t=7), strong (t=13), strongest (t=15)"
|
||||
"balanced" "balanced;small;strong;strongest")
|
||||
|
||||
# Codeword count. Plain cache STRING (not add_option) so a number is kept
|
||||
# rather than reduced to yes/no. The cache name deliberately differs from
|
||||
# the C macro: the loop that turns WOLFSSL_DEFINITIONS into CMake vars
|
||||
# before configure_file() would otherwise clobber this cache entry.
|
||||
# NOTE: setting WC_PUF_NUM_CODEWORDS through raw CMAKE_C_FLAGS bypasses
|
||||
# WOLFSSL_DEFINITIONS and so can never reach the generated options.h,
|
||||
# leaving the application and library with different sizeof(wc_PufCtx).
|
||||
# Use this option instead.
|
||||
set(WOLFSSL_PUF_NUM_CODEWORDS "" CACHE STRING
|
||||
"Number of PUF BCH codewords (1-4095, default: 16)")
|
||||
|
||||
list(APPEND WOLFSSL_DEFINITIONS
|
||||
"-DWOLFSSL_PUF"
|
||||
"-DWOLFSSL_PUF_SRAM"
|
||||
"-DHAVE_HKDF")
|
||||
if(WOLFSSL_PUF_NUM_CODEWORDS)
|
||||
if(NOT WOLFSSL_PUF_NUM_CODEWORDS MATCHES "^[0-9]+$" OR
|
||||
WOLFSSL_PUF_NUM_CODEWORDS LESS 1 OR
|
||||
WOLFSSL_PUF_NUM_CODEWORDS GREATER 4095)
|
||||
message(FATAL_ERROR
|
||||
"WOLFSSL_PUF_NUM_CODEWORDS must be an integer from 1 to 4095")
|
||||
endif()
|
||||
list(APPEND WOLFSSL_DEFINITIONS
|
||||
"-DWC_PUF_NUM_CODEWORDS=${WOLFSSL_PUF_NUM_CODEWORDS}")
|
||||
endif()
|
||||
if(WOLFSSL_PUF_PROFILE STREQUAL "small")
|
||||
list(APPEND WOLFSSL_DEFINITIONS "-DWC_PUF_BCH_T=7")
|
||||
elseif(WOLFSSL_PUF_PROFILE STREQUAL "strong")
|
||||
list(APPEND WOLFSSL_DEFINITIONS "-DWC_PUF_BCH_T=13")
|
||||
elseif(WOLFSSL_PUF_PROFILE STREQUAL "strongest")
|
||||
list(APPEND WOLFSSL_DEFINITIONS "-DWC_PUF_BCH_T=15")
|
||||
elseif(NOT WOLFSSL_PUF_PROFILE STREQUAL "balanced")
|
||||
message(FATAL_ERROR
|
||||
"WOLFSSL_PUF_PROFILE must be balanced, small, strong, or strongest")
|
||||
endif()
|
||||
override_cache(WOLFSSL_HKDF "yes")
|
||||
endif()
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ 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
|
||||
(`--enable-puf[=small|balanced|strong|strongest]`, selecting the BCH error-correction
|
||||
strength). An example exists at
|
||||
[SRAM PUF](https://github.com/wolfSSL/wolfssl-examples/tree/master/puf).
|
||||
|
||||
## Why Choose wolfSSL?
|
||||
|
||||
@@ -121,6 +121,16 @@ extern "C" {
|
||||
#cmakedefine HAVE_HASHDRBG
|
||||
#undef HAVE_HKDF
|
||||
#cmakedefine HAVE_HKDF
|
||||
#undef WOLFSSL_PUF
|
||||
#cmakedefine WOLFSSL_PUF
|
||||
#undef WOLFSSL_PUF_SRAM
|
||||
#cmakedefine WOLFSSL_PUF_SRAM
|
||||
#undef WOLFSSL_PUF_TEST
|
||||
#cmakedefine WOLFSSL_PUF_TEST
|
||||
#undef WC_PUF_BCH_T
|
||||
#cmakedefine WC_PUF_BCH_T @WC_PUF_BCH_T@
|
||||
#undef WC_PUF_NUM_CODEWORDS
|
||||
#cmakedefine WC_PUF_NUM_CODEWORDS @WC_PUF_NUM_CODEWORDS@
|
||||
#undef HAVE_HPKE
|
||||
#cmakedefine HAVE_HPKE
|
||||
#undef HAVE_KEYING_MATERIAL
|
||||
|
||||
23
configure.ac
23
configure.ac
@@ -8596,16 +8596,31 @@ fi
|
||||
|
||||
# PUF
|
||||
AC_ARG_ENABLE([puf],
|
||||
[AS_HELP_STRING([--enable-puf],[Enable SRAM PUF support (default: disabled)])],
|
||||
[AS_HELP_STRING([--enable-puf],[Enable SRAM PUF: profiles small/balanced/strong/strongest = t 7/10/13/15 (default: disabled)])],
|
||||
[ ENABLED_PUF=$enableval ],
|
||||
[ ENABLED_PUF=no ]
|
||||
)
|
||||
|
||||
if test "$ENABLED_PUF" = "yes"
|
||||
if test "$ENABLED_PUF" != "no"
|
||||
then
|
||||
# Map the profile keyword to a BCH error-correction strength (t). An
|
||||
# empty PUF_BCH_T keeps the puf.h default of t=10.
|
||||
PUF_BCH_T=""
|
||||
PUF_PROFILE="balanced (t=10)"
|
||||
case "$ENABLED_PUF" in
|
||||
yes|balanced) PUF_BCH_T="" ; PUF_PROFILE="balanced (t=10)" ;;
|
||||
small) PUF_BCH_T="7" ; PUF_PROFILE="small (t=7)" ;;
|
||||
strong) PUF_BCH_T="13" ; PUF_PROFILE="strong (t=13)" ;;
|
||||
strongest) PUF_BCH_T="15" ; PUF_PROFILE="strongest (t=15)" ;;
|
||||
*) AC_MSG_ERROR([unknown --enable-puf value "$ENABLED_PUF"; use yes, small, balanced, strong, or strongest]) ;;
|
||||
esac
|
||||
AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_PUF -DWOLFSSL_PUF_SRAM"
|
||||
AS_IF([test "x$PUF_BCH_T" != "x"],
|
||||
[AM_CFLAGS="$AM_CFLAGS -DWC_PUF_BCH_T=$PUF_BCH_T"])
|
||||
AS_IF([test "$ENABLED_HKDF" != "yes"],
|
||||
[ENABLED_HKDF="yes"; AM_CFLAGS="$AM_CFLAGS -DHAVE_HKDF"])
|
||||
# normalize so downstream conditionals/summary see a plain yes
|
||||
ENABLED_PUF="yes"
|
||||
fi
|
||||
|
||||
# PUF test mode
|
||||
@@ -13976,7 +13991,11 @@ 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"
|
||||
if test "$ENABLED_PUF" = "yes"; then
|
||||
echo " * PUF: $ENABLED_PUF, profile $PUF_PROFILE"
|
||||
else
|
||||
echo " * PUF: $ENABLED_PUF"
|
||||
fi
|
||||
echo ""
|
||||
echo "---"
|
||||
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
/*!
|
||||
\ingroup PUF
|
||||
|
||||
The SRAM PUF uses a configurable BCH(127,k,t) fuzzy extractor over GF(2^7)
|
||||
with HKDF key derivation. WC_PUF_BCH_T selects the error-correction
|
||||
strength (t=7, 10 default, 13, or 15) and WC_PUF_NUM_CODEWORDS (default 16)
|
||||
trades SRAM footprint and helper-data size (WC_PUF_HELPER_BYTES) against
|
||||
derived-key entropy. Only the (n - k) parity bits per codeword carry
|
||||
information; the leading k bits of each helper codeword are identically
|
||||
zero, so size OTP/flash accordingly. WC_PUF_HELPER_COMPACT stores only
|
||||
those parity bits, shrinking helper data to 39-72% of the default. It
|
||||
changes the stored format and so is opt-in; the default layout stays
|
||||
compatible with helper data enrolled by wolfSSL 5.9.2.
|
||||
|
||||
Enrollment and reconstruction must use identical WC_PUF_BCH_T and
|
||||
WC_PUF_NUM_CODEWORDS (and the same hash); persist WC_PUF_PROFILE_ID with
|
||||
the helper data at enrollment and pass it to wc_PufReconstructEx, or
|
||||
compare it against wc_PufGetProfileId(), to catch a build mismatch.
|
||||
|
||||
For a complete bare-metal example (tested on NUCLEO-H563ZI), see
|
||||
https://github.com/wolfSSL/wolfssl-examples/tree/master/puf
|
||||
*/
|
||||
@@ -32,7 +48,9 @@ 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.
|
||||
point to a NOLOAD linker section to preserve the power-on state. The
|
||||
required size, WC_PUF_RAW_BYTES, scales with WC_PUF_NUM_CODEWORDS
|
||||
(256 bytes at the default 16 codewords).
|
||||
|
||||
\return 0 on success
|
||||
\return BAD_FUNC_ARG if ctx or sramAddr is NULL
|
||||
@@ -45,7 +63,7 @@ int wc_PufInit(wc_PufCtx* ctx);
|
||||
_Example_
|
||||
\code
|
||||
__attribute__((section(".puf_sram")))
|
||||
static volatile uint8_t puf_sram[256];
|
||||
static volatile uint8_t puf_sram[WC_PUF_RAW_BYTES];
|
||||
wc_PufReadSram(&ctx, (const byte*)puf_sram, sizeof(puf_sram));
|
||||
\endcode
|
||||
|
||||
@@ -58,9 +76,10 @@ 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.
|
||||
\brief Perform PUF enrollment. Encodes raw SRAM using the selected
|
||||
BCH(127,k,t) profile (WC_PUF_BCH_T) and generates public helper data
|
||||
(WC_PUF_HELPER_BYTES). After enrollment the context is ready for key
|
||||
derivation and identity retrieval.
|
||||
|
||||
\return 0 on success
|
||||
\return BAD_FUNC_ARG if ctx is NULL
|
||||
@@ -84,8 +103,9 @@ 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.
|
||||
data. BCH error correction corrects up to WC_PUF_BCH_T bit flips per
|
||||
127-bit codeword. The helper data and build configuration must match the
|
||||
enrollment that produced them.
|
||||
|
||||
\return 0 on success
|
||||
\return BAD_FUNC_ARG if ctx or helperData is NULL
|
||||
@@ -166,6 +186,118 @@ int wc_PufDeriveKey(wc_PufCtx* ctx, const byte* info, word32 infoSz,
|
||||
*/
|
||||
int wc_PufGetIdentity(wc_PufCtx* ctx, byte* id, word32 idSz);
|
||||
|
||||
/*!
|
||||
\ingroup PUF
|
||||
|
||||
\brief Report the compile-time PUF profile parameters: field size m,
|
||||
codeword length n, message length k, error-correction capability t, and
|
||||
the number of codewords. Each output pointer is optional (may be NULL), but
|
||||
an all-NULL call is treated as a usage error. Enrollment and reconstruction
|
||||
firmware must agree on all of these (and the hash); persist
|
||||
WC_PUF_PROFILE_ID (which also encodes the hash selection) with the helper
|
||||
data and compare before reconstruction to detect a build mismatch.
|
||||
|
||||
\return 0 on success
|
||||
\return BAD_FUNC_ARG if every output pointer is NULL
|
||||
|
||||
\param m optional output for the GF field exponent (7 for GF(2^7))
|
||||
\param n optional output for the codeword length (127)
|
||||
\param k optional output for the message length (per WC_PUF_BCH_T)
|
||||
\param t optional output for the error-correction capability (WC_PUF_BCH_T)
|
||||
\param numCodewords optional output for WC_PUF_NUM_CODEWORDS
|
||||
|
||||
_Example_
|
||||
\code
|
||||
int t, numCodewords;
|
||||
wc_PufGetParams(NULL, NULL, NULL, &t, &numCodewords);
|
||||
\endcode
|
||||
|
||||
\sa wc_PufEnroll
|
||||
\sa wc_PufReconstruct
|
||||
*/
|
||||
int wc_PufGetParams(int* m, int* n, int* k, int* t, int* numCodewords);
|
||||
|
||||
/*!
|
||||
\ingroup PUF
|
||||
|
||||
\brief Return the profile fingerprint the LIBRARY was compiled with. The
|
||||
WC_PUF_PROFILE_ID macro necessarily reflects the including application's
|
||||
own build, so comparing the two detects a library/application mismatch -
|
||||
including a differing hash selection - that no length check can see.
|
||||
|
||||
\return the library's WC_PUF_PROFILE_ID
|
||||
|
||||
_Example_
|
||||
\code
|
||||
if (wc_PufGetProfileId() != WC_PUF_PROFILE_ID) {
|
||||
/* library and application were built with different PUF settings */
|
||||
}
|
||||
\endcode
|
||||
|
||||
\sa wc_PufGetParams
|
||||
\sa wc_PufReconstructEx
|
||||
*/
|
||||
word32 wc_PufGetProfileId(void);
|
||||
|
||||
/*!
|
||||
\ingroup PUF
|
||||
|
||||
\brief Copy out the enrollment helper data. Use this rather than reading
|
||||
wc_PufCtx.helperData directly: the size varies with the selected profile
|
||||
and with WC_PUF_HELPER_COMPACT.
|
||||
|
||||
\return 0 on success
|
||||
\return BAD_FUNC_ARG if ctx or helper is NULL
|
||||
\return PUF_ENROLL_E if the context is not enrolled, or helperSz is
|
||||
less than WC_PUF_HELPER_BYTES
|
||||
|
||||
\param ctx pointer to an enrolled wc_PufCtx
|
||||
\param helper output buffer for the helper data
|
||||
\param helperSz size of helper in bytes (>= WC_PUF_HELPER_BYTES)
|
||||
|
||||
_Example_
|
||||
\code
|
||||
byte helper[WC_PUF_HELPER_BYTES];
|
||||
wc_PufGetHelperData(&ctx, helper, sizeof(helper));
|
||||
\endcode
|
||||
|
||||
\sa wc_PufEnroll
|
||||
\sa wc_PufReconstructEx
|
||||
*/
|
||||
int wc_PufGetHelperData(wc_PufCtx* ctx, byte* helper, word32 helperSz);
|
||||
|
||||
/*!
|
||||
\ingroup PUF
|
||||
|
||||
\brief Reconstruct as wc_PufReconstruct, but first check the caller's
|
||||
stored profile id against the library's. Helper-data size does not depend
|
||||
on t in the default layout, so the length check alone cannot detect helper
|
||||
data enrolled by a differently configured build - which would otherwise
|
||||
decode to a silently wrong key. Persist WC_PUF_PROFILE_ID alongside the
|
||||
helper data at enrollment and pass it back here.
|
||||
|
||||
\return 0 on success
|
||||
\return BAD_FUNC_ARG if ctx or helperData is NULL, or profileId does not
|
||||
match the library's profile
|
||||
\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 a previous enrollment
|
||||
\param helperSz size of helper data (>= WC_PUF_HELPER_BYTES)
|
||||
\param profileId the WC_PUF_PROFILE_ID recorded at enrollment
|
||||
|
||||
_Example_
|
||||
\code
|
||||
wc_PufReconstructEx(&ctx, helper, sizeof(helper), savedProfileId);
|
||||
\endcode
|
||||
|
||||
\sa wc_PufReconstruct
|
||||
\sa wc_PufGetProfileId
|
||||
*/
|
||||
int wc_PufReconstructEx(wc_PufCtx* ctx, const byte* helperData,
|
||||
word32 helperSz, word32 profileId);
|
||||
|
||||
/*!
|
||||
\ingroup PUF
|
||||
|
||||
@@ -198,7 +330,7 @@ int wc_PufZeroize(wc_PufCtx* ctx);
|
||||
|
||||
\param ctx pointer to wc_PufCtx
|
||||
\param data pointer to synthetic SRAM data
|
||||
\param sz size of data (>= WC_PUF_RAW_BYTES, 256 bytes)
|
||||
\param sz size of data (>= WC_PUF_RAW_BYTES)
|
||||
|
||||
_Example_
|
||||
\code
|
||||
|
||||
303
scripts/puf_bch_genpoly.py
Normal file
303
scripts/puf_bch_genpoly.py
Normal file
@@ -0,0 +1,303 @@
|
||||
#!/usr/bin/env python3
|
||||
# puf_bch_genpoly.py
|
||||
#
|
||||
# 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
|
||||
|
||||
"""Generate and validate BCH(127, k, t) generator polynomials and
|
||||
known-answer test (KAT) vectors for the wolfCrypt SRAM PUF (puf.c).
|
||||
|
||||
The field is GF(2^7) built from the primitive polynomial
|
||||
p(x) = x^7 + x^3 + 1 (0x89), matching the in-tree gf_exp/gf_log tables.
|
||||
|
||||
Output for each shipped profile:
|
||||
- bch_genpoly[] bytes in puf.c layout (64-bit big-endian, low deg bits,
|
||||
leading x^deg coefficient implicit / masked off)
|
||||
- a few message -> 127-bit systematic codeword KAT vectors
|
||||
|
||||
The t=10 genpoly MUST reproduce the existing in-tree constant
|
||||
{0x21,0xAB,0x81,0x5B,0xC7,0xEC,0x80,0x25}; the script asserts this before
|
||||
emitting any other profile, so a bad field or convention fails loudly.
|
||||
|
||||
This script is deterministic and takes no input. Run:
|
||||
python3 scripts/puf_bch_genpoly.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
M = 7
|
||||
N = (1 << M) - 1 # 127
|
||||
PRIM = 0x89 # x^7 + x^3 + 1
|
||||
|
||||
# Profiles: t -> k (n-k = deg of generator polynomial). Only valid BCH(127)
|
||||
# narrow-sense designed-distance profiles. See puf.h profile table.
|
||||
PROFILES = [
|
||||
(7, 78),
|
||||
(10, 64), # default; must match in-tree constant
|
||||
(13, 50),
|
||||
(15, 36),
|
||||
]
|
||||
|
||||
# The shipped tables live here; --check parses them out and diffs.
|
||||
PUF_C = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||
"..", "wolfcrypt", "src", "puf.c")
|
||||
|
||||
|
||||
def parse_puf_c(path):
|
||||
"""Extract {t: (genpoly_bytes, deg)} from the #if WC_PUF_BCH_T ladder in
|
||||
wolfcrypt/src/puf.c. Parsing the real source is the point: a hardcoded
|
||||
copy in this script would still pass if someone edited puf.c."""
|
||||
src = open(path).read()
|
||||
out = {}
|
||||
# each arm: "WC_PUF_BCH_T == <t>" .. "bch_genpoly[..] = { .. };" .. "DEG <d>"
|
||||
pat = re.compile(
|
||||
r"WC_PUF_BCH_T\s*==\s*(\d+)\s*\n"
|
||||
r".*?bch_genpoly\s*\[[^\]]*\]\s*=\s*\{(.*?)\}\s*;"
|
||||
r".*?BCH_GENPOLY_DEG\s+(\d+)",
|
||||
re.S)
|
||||
for m in pat.finditer(src):
|
||||
t = int(m.group(1))
|
||||
body = m.group(2)
|
||||
vals = [int(x, 16) for x in re.findall(r"0[xX]([0-9a-fA-F]{1,2})", body)]
|
||||
out[t] = (vals, int(m.group(3)))
|
||||
return out
|
||||
|
||||
|
||||
def build_gf():
|
||||
"""Return (exp, log) tables for GF(2^7) with primitive poly 0x89."""
|
||||
exp = [0] * (N + 1)
|
||||
log = [0] * (N + 1)
|
||||
x = 1
|
||||
for i in range(N):
|
||||
exp[i] = x
|
||||
log[x] = i
|
||||
x <<= 1
|
||||
if x & (1 << M):
|
||||
x ^= PRIM
|
||||
exp[N] = exp[0] # alpha^127 == alpha^0 == 1 (wrap)
|
||||
return exp, log
|
||||
|
||||
|
||||
EXP, LOG = build_gf()
|
||||
|
||||
|
||||
def gf_mul(a, b):
|
||||
if a == 0 or b == 0:
|
||||
return 0
|
||||
return EXP[(LOG[a] + LOG[b]) % N]
|
||||
|
||||
|
||||
def poly_mul_gf(p, q):
|
||||
"""Multiply two polynomials with GF(2^7) coefficients (lists, index =
|
||||
power of x, element 0 = constant term)."""
|
||||
r = [0] * (len(p) + len(q) - 1)
|
||||
for i, pi in enumerate(p):
|
||||
if pi == 0:
|
||||
continue
|
||||
for j, qj in enumerate(q):
|
||||
if qj == 0:
|
||||
continue
|
||||
r[i + j] ^= gf_mul(pi, qj)
|
||||
return r
|
||||
|
||||
|
||||
def cyclotomic_coset(i):
|
||||
"""The 2-cyclotomic coset of i mod 127."""
|
||||
coset = set()
|
||||
v = i % N
|
||||
while v not in coset:
|
||||
coset.add(v)
|
||||
v = (v * 2) % N
|
||||
return coset
|
||||
|
||||
|
||||
def minimal_poly(i):
|
||||
"""Minimal polynomial of alpha^i: product over its coset of (x + alpha^j).
|
||||
Result has binary coefficients (each 0x00 or 0x01 as a GF element)."""
|
||||
m = [1] # start with 1
|
||||
for j in cyclotomic_coset(i):
|
||||
m = poly_mul_gf(m, [EXP[j], 1]) # (alpha^j + x)
|
||||
# coefficients must be 0 or 1 (binary); verify
|
||||
for c in m:
|
||||
if c not in (0, 1):
|
||||
raise ValueError("minimal poly has non-binary coefficient")
|
||||
return m
|
||||
|
||||
|
||||
def gen_poly(t):
|
||||
"""Narrow-sense BCH generator polynomial = lcm of minimal polynomials of
|
||||
alpha^1 .. alpha^(2t). Returned as a binary-coefficient list (index =
|
||||
power of x)."""
|
||||
used = set()
|
||||
g = [1]
|
||||
for i in range(1, 2 * t + 1):
|
||||
coset = frozenset(cyclotomic_coset(i))
|
||||
if coset in used:
|
||||
continue
|
||||
used.add(coset)
|
||||
g = poly_mul_gf(g, minimal_poly(i))
|
||||
return g
|
||||
|
||||
|
||||
def genpoly_bytes(g):
|
||||
"""Pack g(x) into puf.c's 8..16 byte layout: a big-endian integer whose
|
||||
bit i is coefficient g_i, with the leading coefficient g_deg masked off
|
||||
(implicit). Byte count = ceil(deg / 8)."""
|
||||
deg = len(g) - 1
|
||||
assert g[deg] == 1, "generator must be monic"
|
||||
val = 0
|
||||
for i in range(deg): # exclude leading coeff
|
||||
if g[i]:
|
||||
val |= (1 << i)
|
||||
nbytes = (deg + 7) // 8
|
||||
# 64-bit style big-endian pack sized to nbytes (matches puf.c: byte 0 is
|
||||
# the most significant, top bit of byte 0 is the implicit/masked slot).
|
||||
out = []
|
||||
for b in range(nbytes):
|
||||
shift = 8 * (nbytes - 1 - b)
|
||||
out.append((val >> shift) & 0xFF)
|
||||
return deg, out
|
||||
|
||||
|
||||
def bch_encode_ref(msg_bits, g):
|
||||
"""Reference systematic encoder matching puf.c bch_encode(): codeword =
|
||||
[msg(k) | parity(deg)] MSB-first. msg_bits is a list of k bits (MSB-first
|
||||
message bit order). Returns list of n bits (MSB-first)."""
|
||||
deg = len(g) - 1
|
||||
k = len(msg_bits)
|
||||
reg = [0] * deg # remainder register, reg[0]=MSB
|
||||
for bit in msg_bits:
|
||||
fb = bit ^ reg[0]
|
||||
reg = reg[1:] + [0] # shift left
|
||||
if fb:
|
||||
# XOR non-leading generator coefficients g_{deg-1..0} into reg
|
||||
for p in range(deg):
|
||||
# reg position p holds coefficient of x^(deg-1-p)
|
||||
if g[deg - 1 - p]:
|
||||
reg[p] ^= 1
|
||||
return list(msg_bits) + reg # [msg | parity], n bits
|
||||
|
||||
|
||||
def syndromes_zero(cw_bits, t):
|
||||
"""Return True iff every narrow-sense syndrome S_1..S_2t of the codeword is
|
||||
zero, i.e. the bit list is a genuine BCH codeword. Mirrors puf.c's
|
||||
bch_syndrome_eval: bit j (MSB-first) is the coefficient of x^(n-1-j), so
|
||||
S_root = c(alpha^root) = XOR of alpha^(root*(n-1-j)) over the set bits."""
|
||||
n = len(cw_bits)
|
||||
for root in range(1, 2 * t + 1):
|
||||
s = 0
|
||||
for j, b in enumerate(cw_bits):
|
||||
if b:
|
||||
s ^= EXP[(root * (n - 1 - j)) % N]
|
||||
if s != 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def bits_to_hex(bits):
|
||||
"""Pack an MSB-first bit list into bytes, byte-rounded."""
|
||||
out = bytearray((len(bits) + 7) // 8)
|
||||
for i, b in enumerate(bits):
|
||||
if b:
|
||||
out[i // 8] |= 1 << (7 - (i % 8))
|
||||
return list(out)
|
||||
|
||||
|
||||
def fmt_bytes(bs):
|
||||
return "{ " + ", ".join("0x%02X" % b for b in bs) + " }"
|
||||
|
||||
|
||||
def main():
|
||||
check = "--check" in sys.argv[1:]
|
||||
|
||||
# Sanity: exp table spot checks against known values.
|
||||
assert EXP[1] == 0x02 and EXP[7] == 0x09, "GF(2^7) table mismatch"
|
||||
|
||||
shipped = parse_puf_c(PUF_C) if check else {}
|
||||
if check and len(shipped) != len(PROFILES):
|
||||
raise SystemExit("--check: parsed %d tables from %s, expected %d"
|
||||
% (len(shipped), PUF_C, len(PROFILES)))
|
||||
|
||||
if not check:
|
||||
print("/* Generated by scripts/puf_bch_genpoly.py - do not hand-edit */")
|
||||
print("/* Field GF(2^7), primitive poly 0x89 (x^7 + x^3 + 1) */\n")
|
||||
|
||||
bad = 0
|
||||
for t, k in PROFILES:
|
||||
g = gen_poly(t)
|
||||
deg, bs = genpoly_bytes(g)
|
||||
if deg != N - k:
|
||||
raise SystemExit(
|
||||
"t=%d: computed deg %d != n-k %d" % (t, deg, N - k))
|
||||
|
||||
# KAT self-check: the reference encoder must yield a genuine BCH
|
||||
# codeword (all 2t syndromes zero), confirming both the generator
|
||||
# polynomial and the encoder algebra for this profile.
|
||||
kats = []
|
||||
for seed in (0, 1, 2):
|
||||
msg_bits = [((seed * 131 + i * 37) >> (i % 5)) & 1
|
||||
for i in range(k)]
|
||||
cw = bch_encode_ref(msg_bits, g)
|
||||
if not syndromes_zero(cw, t):
|
||||
raise SystemExit(
|
||||
"t=%d: KAT codeword has nonzero syndrome" % t)
|
||||
kats.append((msg_bits, cw))
|
||||
|
||||
if check:
|
||||
# Diff the freshly derived table against what puf.c actually
|
||||
# ships. This is the whole point of --check: a copy of the bytes
|
||||
# kept inside this script would pass even if puf.c were edited.
|
||||
if t not in shipped:
|
||||
print("FAIL t=%d: no table found in puf.c" % t)
|
||||
bad += 1
|
||||
continue
|
||||
got, gotdeg = shipped[t]
|
||||
if got != bs:
|
||||
print("FAIL t=%d: puf.c has %s, expected %s"
|
||||
% (t, fmt_bytes(got), fmt_bytes(bs)))
|
||||
bad += 1
|
||||
elif gotdeg != deg:
|
||||
print("FAIL t=%d: puf.c BCH_GENPOLY_DEG %d, expected %d"
|
||||
% (t, gotdeg, deg))
|
||||
bad += 1
|
||||
else:
|
||||
print("ok t=%d k=%d deg=%d %s" % (t, k, deg, fmt_bytes(bs)))
|
||||
continue
|
||||
|
||||
print("/* t=%d k=%d deg=%d (%d parity bytes) */" %
|
||||
(t, k, deg, len(bs)))
|
||||
print("static const byte bch_genpoly_t%d[%d] =" % (t, len(bs)))
|
||||
print(" %s;" % fmt_bytes(bs))
|
||||
for msg_bits, cw in kats:
|
||||
print("/* KAT msg=%s" % fmt_bytes(bits_to_hex(msg_bits)))
|
||||
print(" cw =%s */" % fmt_bytes(bits_to_hex(cw)))
|
||||
print()
|
||||
|
||||
if check:
|
||||
if bad:
|
||||
raise SystemExit("%d generator polynomial(s) in puf.c do NOT "
|
||||
"match; regenerate or revert." % bad)
|
||||
print("all %d shipped generator polynomials verified against %s"
|
||||
% (len(PROFILES), os.path.normpath(PUF_C)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -133,6 +133,42 @@ static WC_INLINE byte gf_inv(byte a)
|
||||
return gf_exp[GF_MASK - gf_log[a]];
|
||||
}
|
||||
|
||||
/* ---- Bit helpers (MSB-first bit ordering within a byte array) ---- */
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
/* Copy nbits (MSB-first) from the front of src into dst. Copies the whole-byte
|
||||
* prefix with a single memcpy and bit-loops only the < 8 trailing bits, so a
|
||||
* byte-aligned length (e.g. t=10 k=64) is byte-for-byte a plain memcpy. */
|
||||
static void copyBits(byte* dst, const byte* src, int nbits)
|
||||
{
|
||||
int full = nbits / 8;
|
||||
int i;
|
||||
|
||||
if (full > 0)
|
||||
XMEMCPY(dst, src, (word32)full);
|
||||
if ((nbits & 7) != 0) {
|
||||
dst[full] = 0;
|
||||
for (i = full * 8; i < nbits; i++)
|
||||
setBit(dst, i, getBit(src, i));
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- BCH syndrome computation ---- */
|
||||
|
||||
/* Evaluate syndrome: S_root = c(alpha^root) where codeword bits are packed
|
||||
@@ -156,13 +192,25 @@ static byte bch_syndrome_eval(const byte* codeword, int root)
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Compute 2t syndromes S[1..2t] */
|
||||
/* Compute 2t syndromes S[1..2t]. The codeword has GF(2) coefficients, so
|
||||
* c(x)^2 = c(x^2) and therefore S_2i = S_i^2: only the t odd-index syndromes
|
||||
* need a full evaluation pass, each even one is a single squaring. Syndromes
|
||||
* dominate reconstruct and are computed twice per codeword (the second time
|
||||
* for the post-correction recheck), so this roughly halves that cost. */
|
||||
static void bch_syndromes(const byte* codeword, byte* syndromes)
|
||||
{
|
||||
byte s;
|
||||
int i;
|
||||
for (i = 1; i <= 2 * WC_PUF_BCH_T; i++) {
|
||||
|
||||
for (i = 1; i <= 2 * WC_PUF_BCH_T; i += 2) {
|
||||
syndromes[i] = bch_syndrome_eval(codeword, i);
|
||||
}
|
||||
/* ascending, so S[i/2] is always already computed when S[i] is filled */
|
||||
for (i = 2; i <= 2 * WC_PUF_BCH_T; i += 2) {
|
||||
s = syndromes[i / 2];
|
||||
syndromes[i] = (s == 0) ? (byte)0 :
|
||||
gf_exp[(2 * (int)gf_log[s]) % GF_MASK];
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Berlekamp-Massey algorithm ---- */
|
||||
@@ -266,92 +314,116 @@ static int bch_chien_search(const byte* sigma, int deg, int* errPos)
|
||||
return count;
|
||||
}
|
||||
|
||||
/* ---- BCH encode: compute parity for 64-bit message ---- */
|
||||
/* ---- BCH encode: compute parity for a k-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. */
|
||||
/* Generator polynomial for the selected BCH(127,k,t) profile over GF(2).
|
||||
* g(x) is the product (lcm) of the minimal polynomials of alpha^1..alpha^(2t);
|
||||
* its degree is n - k. The bytes below are generated and validated by
|
||||
* scripts/puf_bch_genpoly.py (which reproduces the t=10 constant exactly as
|
||||
* an oracle before emitting the others). Do NOT hand-edit: a single wrong
|
||||
* bit silently corrupts every derived key.
|
||||
*
|
||||
* Storage layout: a big-endian integer of WC_PUF_PARITY_BYTES bytes whose
|
||||
* bit i (LSB = 0) is coefficient g_i for i in [0, deg). The leading
|
||||
* coefficient g_deg (== 1) is implicit; any bits above deg are zero. */
|
||||
#if WC_PUF_BCH_T == 7
|
||||
static const byte bch_genpoly[WC_PUF_PARITY_BYTES] = {
|
||||
0x00, 0xC9, 0x80, 0x11, 0xD8, 0xB0, 0x4D
|
||||
};
|
||||
#define BCH_GENPOLY_DEG 49
|
||||
#elif WC_PUF_BCH_T == 10
|
||||
static const byte bch_genpoly[WC_PUF_PARITY_BYTES] = {
|
||||
0x21, 0xAB, 0x81, 0x5B, 0xC7, 0xEC, 0x80, 0x25
|
||||
};
|
||||
#define BCH_GENPOLY_DEG 63
|
||||
#elif WC_PUF_BCH_T == 13
|
||||
static const byte bch_genpoly[WC_PUF_PARITY_BYTES] = {
|
||||
0x0C, 0x93, 0x52, 0xAA, 0x6C, 0xC0, 0x54, 0x46, 0x83, 0x11
|
||||
};
|
||||
#define BCH_GENPOLY_DEG 77
|
||||
#elif WC_PUF_BCH_T == 15
|
||||
static const byte bch_genpoly[WC_PUF_PARITY_BYTES] = {
|
||||
0x04, 0xCC, 0x3C, 0xDB, 0x54, 0x87, 0xA2, 0x4F, 0xA5, 0xF3, 0xA3, 0xDD
|
||||
};
|
||||
#define BCH_GENPOLY_DEG 91
|
||||
#else
|
||||
#error "No generator polynomial for the selected WC_PUF_BCH_T"
|
||||
#endif
|
||||
|
||||
/* 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
|
||||
};
|
||||
/* Cross-check the shipped polynomial degree against the derived n - k. */
|
||||
#if BCH_GENPOLY_DEG != WC_PUF_BCH_DEG
|
||||
#error "bch_genpoly degree does not match WC_PUF_BCH_DEG (n - k)"
|
||||
#endif
|
||||
|
||||
/* 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)]. */
|
||||
/* Mask keeping only the used bits of the most-significant parity byte
|
||||
* (the register holds exactly WC_PUF_BCH_DEG bits; higher bits are unused). */
|
||||
#define BCH_REG_MSB_MASK \
|
||||
((byte)((1 << (((WC_PUF_BCH_DEG - 1) & 7) + 1)) - 1))
|
||||
|
||||
/* Read big-endian bit b (LSB = 0) from a parity-register byte array. */
|
||||
static WC_INLINE byte regGetBit(const byte* reg, int b)
|
||||
{
|
||||
return (reg[WC_PUF_PARITY_BYTES - 1 - (b / 8)] >> (b % 8)) & 1;
|
||||
}
|
||||
|
||||
/* Encode a k-bit message into a 127-bit codeword.
|
||||
* msg: WC_PUF_MSG_BYTES bytes (k bits, MSB-first), output: WC_PUF_CW_BYTES
|
||||
* bytes (127 bits, MSB aligned). Systematic: codeword = [msg(k) | parity(deg)].
|
||||
*
|
||||
* The parity register is a big-endian integer of WC_PUF_PARITY_BYTES bytes
|
||||
* holding WC_PUF_BCH_DEG bits; this generalizes the fixed degree-63 LFSR and
|
||||
* reproduces the t=10 output byte-for-byte. */
|
||||
static void bch_encode(const byte* msg, byte* codeword)
|
||||
{
|
||||
byte shift_reg[8]; /* 63-bit shift register for parity */
|
||||
byte shift_reg[WC_PUF_PARITY_BYTES];
|
||||
int i, j;
|
||||
|
||||
XMEMSET(shift_reg, 0, sizeof(shift_reg));
|
||||
|
||||
/* Process each of the 64 message bits */
|
||||
/* Process each of the k message bits (MSB-first) */
|
||||
for (i = 0; i < WC_PUF_BCH_K; i++) {
|
||||
int byteIdx = i / 8;
|
||||
int bitIdx = 7 - (i % 8);
|
||||
byte msgBit = (msg[byteIdx] >> bitIdx) & 1;
|
||||
byte msgBit = getBit(msg, i);
|
||||
|
||||
/* feedback = msgBit XOR MSB of shift register */
|
||||
byte fb = msgBit ^ ((shift_reg[0] >> 6) & 1);
|
||||
/* feedback = msgBit XOR MSB of shift register (bit deg-1) */
|
||||
byte fb = msgBit ^ regGetBit(shift_reg, WC_PUF_BCH_DEG - 1);
|
||||
|
||||
/* shift register left by 1 */
|
||||
for (j = 0; j < 7; j++) {
|
||||
/* shift register left by 1 (big-endian, byte 0 most significant) */
|
||||
for (j = 0; j < WC_PUF_PARITY_BYTES - 1; 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;
|
||||
shift_reg[WC_PUF_PARITY_BYTES - 1] =
|
||||
(byte)(shift_reg[WC_PUF_PARITY_BYTES - 1] << 1);
|
||||
/* keep the register at exactly WC_PUF_BCH_DEG bits */
|
||||
shift_reg[0] &= BCH_REG_MSB_MASK;
|
||||
|
||||
/* XOR with generator if feedback is 1 */
|
||||
if (fb) {
|
||||
for (j = 0; j < 8; j++) {
|
||||
for (j = 0; j < WC_PUF_PARITY_BYTES; 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;
|
||||
/* unused high bits of g are zero; mask defensively */
|
||||
shift_reg[0] &= BCH_REG_MSB_MASK;
|
||||
}
|
||||
}
|
||||
|
||||
/* Build codeword: [msg(64 bits) | parity(63 bits)] = 127 bits */
|
||||
XMEMSET(codeword, 0, 16);
|
||||
XMEMCPY(codeword, msg, 8); /* message in first 64 bits */
|
||||
/* Build codeword: [msg(k bits) | parity(deg bits)] = 127 bits */
|
||||
XMEMSET(codeword, 0, WC_PUF_CW_BYTES);
|
||||
for (i = 0; i < WC_PUF_BCH_K; i++) {
|
||||
setBit(codeword, i, getBit(msg, i));
|
||||
}
|
||||
|
||||
/* 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);
|
||||
}
|
||||
/* parity: register MSB (bit deg-1) first, into codeword positions k..n-1 */
|
||||
for (i = 0; i < WC_PUF_BCH_DEG; i++) {
|
||||
setBit(codeword, WC_PUF_BCH_K + i,
|
||||
regGetBit(shift_reg, WC_PUF_BCH_DEG - 1 - i));
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- BCH decode ---- */
|
||||
|
||||
/* Decode 127-bit codeword, correct up to t=10 errors.
|
||||
* Extracts 64-bit message into msg (8 bytes).
|
||||
/* Decode 127-bit codeword, correct up to WC_PUF_BCH_T errors.
|
||||
* Extracts the k-bit message into msg (WC_PUF_MSG_BYTES bytes).
|
||||
* Returns 0 on success, negative on uncorrectable error. */
|
||||
static int bch_decode(byte* codeword, byte* msg)
|
||||
{
|
||||
@@ -374,7 +446,7 @@ static int bch_decode(byte* codeword, byte* msg)
|
||||
|
||||
if (allZero) {
|
||||
/* no errors, extract message directly */
|
||||
XMEMCPY(msg, codeword, 8);
|
||||
copyBits(msg, codeword, WC_PUF_BCH_K);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -406,8 +478,8 @@ static int bch_decode(byte* codeword, byte* msg)
|
||||
return PUF_RECONSTRUCT_E;
|
||||
}
|
||||
|
||||
/* extract message (first 64 bits) */
|
||||
XMEMCPY(msg, codeword, 8);
|
||||
/* extract message (first k bits) */
|
||||
copyBits(msg, codeword, WC_PUF_BCH_K);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -415,39 +487,25 @@ static int bch_decode(byte* codeword, byte* msg)
|
||||
/* 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);
|
||||
XMEMSET(cw, 0, WC_PUF_CW_BYTES);
|
||||
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 */
|
||||
/* Store the retained region of a helper codeword into helper data at the
|
||||
* given bit offset. With WC_PUF_HELPER_COMPACT only the (n-k) parity bits are
|
||||
* kept (the leading k bits are identically zero); otherwise the full n bits
|
||||
* are stored, matching the layout shipped in 5.9.2. */
|
||||
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));
|
||||
for (i = 0; i < WC_PUF_HELPER_LEN; i++) {
|
||||
setBit(helper, bitOffset + i, getBit(cw, WC_PUF_HELPER_OFF + i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -489,10 +547,10 @@ int wc_PufReadSram(wc_PufCtx* ctx, const byte* sramAddr, word32 sramSz)
|
||||
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];
|
||||
byte msg[WC_PUF_MSG_BYTES]; /* k-bit message */
|
||||
byte cw[WC_PUF_CW_BYTES]; /* 127-bit codeword */
|
||||
byte rawCw[WC_PUF_CW_BYTES];
|
||||
byte helperCw[WC_PUF_CW_BYTES];
|
||||
|
||||
WOLFSSL_ENTER("wc_PufEnroll");
|
||||
|
||||
@@ -519,30 +577,32 @@ int wc_PufEnroll(wc_PufCtx* ctx)
|
||||
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 */
|
||||
/* extract k message bits from raw SRAM */
|
||||
int bitOff = i * 128; /* 128-bit stride per codeword (n=127 fits) */
|
||||
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);
|
||||
/* save stable bits: k bits per codeword, bit-packed */
|
||||
for (j = 0; j < WC_PUF_BCH_K; j++) {
|
||||
setBit(ctx->stableBits, i * WC_PUF_BCH_K + j, getBit(msg, j));
|
||||
}
|
||||
|
||||
/* 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++) {
|
||||
XMEMSET(helperCw, 0, WC_PUF_CW_BYTES);
|
||||
for (j = 0; j < WC_PUF_CW_BYTES; j++) {
|
||||
helperCw[j] = rawCw[j] ^ cw[j];
|
||||
}
|
||||
storeCodeword(ctx->helperData, i * WC_PUF_BCH_N, helperCw);
|
||||
storeCodeword(ctx->helperData, i * WC_PUF_HELPER_LEN, helperCw);
|
||||
}
|
||||
|
||||
/* compute identity = SHA-256(stableBits) */
|
||||
/* compute identity = hash(stableBits) */
|
||||
ret = wc_PufHashDirect(ctx->stableBits, WC_PUF_STABLE_BYTES, ctx->identity);
|
||||
|
||||
/* zeroize sensitive stack buffers */
|
||||
@@ -564,18 +624,25 @@ int wc_PufEnroll(wc_PufCtx* ctx)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int wc_PufReconstruct(wc_PufCtx* ctx, const byte* helperData, word32 helperSz)
|
||||
int wc_PufReconstructEx(wc_PufCtx* ctx, const byte* helperData,
|
||||
word32 helperSz, word32 profileId)
|
||||
{
|
||||
int i, ret;
|
||||
byte rawCw[16];
|
||||
byte helperCw[16];
|
||||
byte noisyCw[16];
|
||||
byte msg[8];
|
||||
byte rawCw[WC_PUF_CW_BYTES];
|
||||
byte helperCw[WC_PUF_CW_BYTES];
|
||||
byte noisyCw[WC_PUF_CW_BYTES];
|
||||
byte msg[WC_PUF_MSG_BYTES];
|
||||
|
||||
WOLFSSL_ENTER("wc_PufReconstruct");
|
||||
WOLFSSL_ENTER("wc_PufReconstructEx");
|
||||
|
||||
if (ctx == NULL || helperData == NULL)
|
||||
return BAD_FUNC_ARG;
|
||||
/* Reject helper data from a different build before touching it: the
|
||||
* length guard cannot catch this on its own, because helper size does not
|
||||
* depend on t in the default layout. A mismatch would otherwise decode to
|
||||
* a silently wrong key. */
|
||||
if (profileId != (word32)WC_PUF_PROFILE_ID)
|
||||
return BAD_FUNC_ARG;
|
||||
if (helperSz < WC_PUF_HELPER_BYTES)
|
||||
return PUF_RECONSTRUCT_E;
|
||||
if (!(ctx->flags & WC_PUF_FLAG_SRAM_SET))
|
||||
@@ -605,13 +672,14 @@ int wc_PufReconstruct(wc_PufCtx* ctx, const byte* helperData, word32 helperSz)
|
||||
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));
|
||||
XMEMSET(helperCw, 0, WC_PUF_CW_BYTES);
|
||||
for (j = 0; j < WC_PUF_HELPER_LEN; j++) {
|
||||
setBit(helperCw, WC_PUF_HELPER_OFF + j,
|
||||
getBit(helperData, i * WC_PUF_HELPER_LEN + j));
|
||||
}
|
||||
|
||||
/* noisy codeword = raw XOR helper */
|
||||
for (j = 0; j < 16; j++) {
|
||||
for (j = 0; j < WC_PUF_CW_BYTES; j++) {
|
||||
noisyCw[j] = rawCw[j] ^ helperCw[j];
|
||||
}
|
||||
|
||||
@@ -633,7 +701,10 @@ int wc_PufReconstruct(wc_PufCtx* ctx, const byte* helperData, word32 helperSz)
|
||||
return PUF_RECONSTRUCT_E;
|
||||
}
|
||||
|
||||
XMEMCPY(ctx->stableBits + i * 8, msg, 8);
|
||||
/* store k stable bits per codeword, bit-packed */
|
||||
for (j = 0; j < WC_PUF_BCH_K; j++) {
|
||||
setBit(ctx->stableBits, i * WC_PUF_BCH_K + j, getBit(msg, j));
|
||||
}
|
||||
}
|
||||
|
||||
/* compute identity */
|
||||
@@ -658,6 +729,14 @@ int wc_PufReconstruct(wc_PufCtx* ctx, const byte* helperData, word32 helperSz)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int wc_PufReconstruct(wc_PufCtx* ctx, const byte* helperData, word32 helperSz)
|
||||
{
|
||||
/* the profile id passed is the library's own, so the check inside the Ex
|
||||
* variant is trivially satisfied and behaviour matches prior releases */
|
||||
return wc_PufReconstructEx(ctx, helperData, helperSz,
|
||||
(word32)WC_PUF_PROFILE_ID);
|
||||
}
|
||||
|
||||
int wc_PufDeriveKey(wc_PufCtx* ctx, const byte* info, word32 infoSz,
|
||||
byte* key, word32 keySz)
|
||||
{
|
||||
@@ -712,6 +791,63 @@ int wc_PufGetIdentity(wc_PufCtx* ctx, byte* id, word32 idSz)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Report the compile-time PUF profile parameters. Each output pointer is
|
||||
* optional (may be NULL). Enrollment and reconstruction firmware must agree
|
||||
* on all of these; persist them (or WC_PUF_PROFILE_ID) with the helper data
|
||||
* and compare before reconstruction to detect a build mismatch. */
|
||||
int wc_PufGetParams(int* m, int* n, int* k, int* t, int* numCodewords)
|
||||
{
|
||||
WOLFSSL_ENTER("wc_PufGetParams");
|
||||
|
||||
/* every output is optional, but an all-NULL call is a programming error;
|
||||
* reject it to match the argument-validation convention of the module */
|
||||
if (m == NULL && n == NULL && k == NULL && t == NULL &&
|
||||
numCodewords == NULL)
|
||||
return BAD_FUNC_ARG;
|
||||
|
||||
if (m != NULL)
|
||||
*m = WC_PUF_BCH_M;
|
||||
if (n != NULL)
|
||||
*n = WC_PUF_BCH_N;
|
||||
if (k != NULL)
|
||||
*k = WC_PUF_BCH_K;
|
||||
if (t != NULL)
|
||||
*t = WC_PUF_BCH_T;
|
||||
if (numCodewords != NULL)
|
||||
*numCodewords = WC_PUF_NUM_CODEWORDS;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Report the profile fingerprint the LIBRARY was compiled with. The
|
||||
* WC_PUF_PROFILE_ID macro necessarily reflects the calling application's own
|
||||
* build, so comparing the two detects a library/application mismatch that no
|
||||
* length check can see. */
|
||||
word32 wc_PufGetProfileId(void)
|
||||
{
|
||||
WOLFSSL_ENTER("wc_PufGetProfileId");
|
||||
|
||||
return (word32)WC_PUF_PROFILE_ID;
|
||||
}
|
||||
|
||||
/* Copy out the enrollment helper data. Applications should use this rather
|
||||
* than reading wc_PufCtx.helperData directly: the size varies with the
|
||||
* selected profile. */
|
||||
int wc_PufGetHelperData(wc_PufCtx* ctx, byte* helper, word32 helperSz)
|
||||
{
|
||||
WOLFSSL_ENTER("wc_PufGetHelperData");
|
||||
|
||||
if (ctx == NULL || helper == NULL)
|
||||
return BAD_FUNC_ARG;
|
||||
if (!(ctx->flags & WC_PUF_FLAG_ENROLLED))
|
||||
return PUF_ENROLL_E;
|
||||
if (helperSz < WC_PUF_HELPER_BYTES)
|
||||
return PUF_ENROLL_E;
|
||||
|
||||
XMEMCPY(helper, ctx->helperData, WC_PUF_HELPER_BYTES);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int wc_PufZeroize(wc_PufCtx* ctx)
|
||||
{
|
||||
WOLFSSL_ENTER("wc_PufZeroize");
|
||||
@@ -741,4 +877,8 @@ int wc_PufSetTestData(wc_PufCtx* ctx, const byte* data, word32 sz)
|
||||
}
|
||||
#endif /* WOLFSSL_PUF_TEST */
|
||||
|
||||
/* implementation-private macros - not part of the public WC_PUF_ namespace */
|
||||
#undef BCH_GENPOLY_DEG
|
||||
#undef BCH_REG_MSB_MASK
|
||||
|
||||
#endif /* WOLFSSL_PUF */
|
||||
|
||||
@@ -24274,6 +24274,34 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t sm4_test(void)
|
||||
#endif
|
||||
|
||||
#ifdef WOLFSSL_PUF
|
||||
|
||||
#if defined(WOLFSSL_PUF_TEST) && defined(HAVE_HKDF) && \
|
||||
(!defined(NO_SHA256) || defined(WOLFSSL_SHA3))
|
||||
/* Deterministic, profile-agnostic fill of the raw SRAM image. */
|
||||
static void puf_fill_sram(byte* sram, word32 sz)
|
||||
{
|
||||
word32 i;
|
||||
for (i = 0; i < sz; i++) {
|
||||
sram[i] = (byte)((i * 167u + 13u) ^ (i << 3));
|
||||
}
|
||||
}
|
||||
|
||||
/* Flip 'count' distinct bits inside one 128-bit codeword block of the raw
|
||||
* SRAM image, staying within the used n=127 bits (bit 127 is unused). Bits
|
||||
* are spread 7 apart so up to t+1 flips land in distinct positions < 127. */
|
||||
static void puf_flip_bits(byte* sram, int block, int count)
|
||||
{
|
||||
int i;
|
||||
int base = block * 128; /* bit offset of the block */
|
||||
for (i = 0; i < count; i++) {
|
||||
int bitPos = base + (i * 7);
|
||||
int byteIdx = bitPos / 8;
|
||||
int bitIdx = 7 - (bitPos % 8);
|
||||
sram[byteIdx] ^= (byte)(1 << bitIdx);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
WOLFSSL_TEST_SUBROUTINE wc_test_ret_t puf_test(void)
|
||||
{
|
||||
#if defined(WOLFSSL_PUF_TEST) && defined(HAVE_HKDF) && \
|
||||
@@ -24284,50 +24312,24 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t puf_test(void)
|
||||
byte key2[WC_PUF_KEY_SZ];
|
||||
byte id1[WC_PUF_ID_SZ];
|
||||
byte id2[WC_PUF_ID_SZ];
|
||||
int block, nblocks;
|
||||
|
||||
/* 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
|
||||
};
|
||||
|
||||
/* deterministic test SRAM, sized to the selected profile. These buffers
|
||||
* scale with WC_PUF_NUM_CODEWORDS, so keep them off the stack under
|
||||
* WOLFSSL_SMALL_STACK (static there, plain stack otherwise). */
|
||||
WOLFSSL_SMALL_STACK_STATIC byte testSram[WC_PUF_RAW_BYTES];
|
||||
/* noisy SRAM: same as testSram but with a few flipped bits */
|
||||
byte noisySram[WC_PUF_RAW_BYTES];
|
||||
byte helperBuf[WC_PUF_HELPER_BYTES];
|
||||
WOLFSSL_SMALL_STACK_STATIC byte noisySram[WC_PUF_RAW_BYTES];
|
||||
WOLFSSL_SMALL_STACK_STATIC byte helperBuf[WC_PUF_HELPER_BYTES];
|
||||
const byte info[] = "puf-test-context";
|
||||
|
||||
WOLFSSL_ENTER("puf_test");
|
||||
|
||||
puf_fill_sram(testSram, (word32)sizeof(testSram));
|
||||
|
||||
/* number of blocks we exercise with noise (cap at 3) */
|
||||
nblocks = WC_PUF_NUM_CODEWORDS < 3 ? WC_PUF_NUM_CODEWORDS : 3;
|
||||
|
||||
/* ---- Test 1: Init ---- */
|
||||
ret = wc_PufInit(&ctx);
|
||||
if (ret != 0)
|
||||
@@ -24388,14 +24390,12 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t puf_test(void)
|
||||
if (XMEMCMP(key1, key2, WC_PUF_KEY_SZ) != 0)
|
||||
return WC_TEST_RET_ENC_NC;
|
||||
|
||||
/* ---- Test 4: Reconstruct with noisy data (few bit flips) ---- */
|
||||
/* ---- Test 4: Reconstruct with noisy data at the correction limit ---- */
|
||||
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 */
|
||||
/* flip exactly WC_PUF_BCH_T bits per block (max the decoder must fix) */
|
||||
for (block = 0; block < nblocks; block++) {
|
||||
puf_flip_bits(noisySram, block, WC_PUF_BCH_T);
|
||||
}
|
||||
|
||||
ret = wc_PufInit(&ctx);
|
||||
if (ret != 0)
|
||||
@@ -24429,13 +24429,16 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t puf_test(void)
|
||||
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) ---- */
|
||||
/* ---- Test 4b: Too many bit errors (exceeds correction limit) ---- *
|
||||
* Flipping t+1 bits must never silently reproduce the CORRECT key: the
|
||||
* decoder either fails, or (if a t+1 pattern lands near another valid
|
||||
* codeword) yields a different identity. Both are acceptable; a matching
|
||||
* identity is not. */
|
||||
{
|
||||
byte tooNoisySram[WC_PUF_RAW_BYTES];
|
||||
WOLFSSL_SMALL_STACK_STATIC 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 */
|
||||
/* flip WC_PUF_BCH_T + 1 bits in block 0 (exceeds correction limit) */
|
||||
puf_flip_bits(tooNoisySram, 0, WC_PUF_BCH_T + 1);
|
||||
|
||||
ret = wc_PufInit(&ctx);
|
||||
if (ret != 0)
|
||||
@@ -24446,9 +24449,19 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t puf_test(void)
|
||||
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;
|
||||
ret = wc_PufReconstruct(&ctx, helperBuf, WC_PUF_HELPER_BYTES);
|
||||
if (ret == 0) {
|
||||
/* decoded to some valid codeword - it must NOT be the enrolled
|
||||
* identity (a wrong-but-stable key is still a failure) */
|
||||
if (wc_PufGetIdentity(&ctx, id2, sizeof(id2)) == 0 &&
|
||||
XMEMCMP(id1, id2, WC_PUF_ID_SZ) == 0)
|
||||
return WC_TEST_RET_ENC_NC;
|
||||
}
|
||||
else if (ret != WC_NO_ERR_TRACE(PUF_RECONSTRUCT_E)) {
|
||||
/* the only acceptable failure here is the correction-limit
|
||||
* rejection; any other code means something unrelated broke */
|
||||
return WC_TEST_RET_ENC_EC(ret);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Test 5: Bad argument checks ---- */
|
||||
@@ -24488,6 +24501,128 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t puf_test(void)
|
||||
!= WC_NO_ERR_TRACE(PUF_DERIVE_KEY_E))
|
||||
return WC_TEST_RET_ENC_NC;
|
||||
|
||||
/* ---- Test 7: wc_PufGetParams reports the compiled-in profile ---- *
|
||||
* This also catches a build-system mismatch where the library and the
|
||||
* including application disagree on the selected profile. */
|
||||
{
|
||||
int pm = 0, pn = 0, pk = 0, pt = 0, pnc = 0;
|
||||
ret = wc_PufGetParams(&pm, &pn, &pk, &pt, &pnc);
|
||||
if (ret != 0)
|
||||
return WC_TEST_RET_ENC_EC(ret);
|
||||
if (pm != WC_PUF_BCH_M || pn != WC_PUF_BCH_N || pk != WC_PUF_BCH_K ||
|
||||
pt != WC_PUF_BCH_T || pnc != WC_PUF_NUM_CODEWORDS)
|
||||
return WC_TEST_RET_ENC_NC;
|
||||
/* every output is optional, but an all-NULL call is a usage error */
|
||||
if (wc_PufGetParams(NULL, NULL, NULL, NULL, NULL)
|
||||
!= WC_NO_ERR_TRACE(BAD_FUNC_ARG))
|
||||
return WC_TEST_RET_ENC_NC;
|
||||
}
|
||||
|
||||
/* ---- Test 8: new accessors and the checked reconstruct path ---- */
|
||||
{
|
||||
byte helper2[WC_PUF_HELPER_BYTES];
|
||||
|
||||
/* the library's compiled-in profile must agree with the macro the
|
||||
* application sees; a disagreement means a build/packaging bug */
|
||||
if (wc_PufGetProfileId() != (word32)WC_PUF_PROFILE_ID)
|
||||
return WC_TEST_RET_ENC_NC;
|
||||
|
||||
/* Test 6 zeroized ctx, so re-enroll to get a live context */
|
||||
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);
|
||||
ret = wc_PufReadSram(&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);
|
||||
|
||||
/* accessor must return exactly what enrollment produced */
|
||||
ret = wc_PufGetHelperData(&ctx, helper2, sizeof(helper2));
|
||||
if (ret != 0)
|
||||
return WC_TEST_RET_ENC_EC(ret);
|
||||
if (XMEMCMP(helper2, helperBuf, WC_PUF_HELPER_BYTES) != 0)
|
||||
return WC_TEST_RET_ENC_NC;
|
||||
if (wc_PufGetHelperData(&ctx, helper2, WC_PUF_HELPER_BYTES - 1)
|
||||
!= WC_NO_ERR_TRACE(PUF_ENROLL_E))
|
||||
return WC_TEST_RET_ENC_NC;
|
||||
if (wc_PufGetHelperData(NULL, helper2, sizeof(helper2))
|
||||
!= WC_NO_ERR_TRACE(BAD_FUNC_ARG))
|
||||
return WC_TEST_RET_ENC_NC;
|
||||
|
||||
/* checked reconstruct: a foreign profile id must be refused before
|
||||
* the helper data is touched, and the matching id must behave
|
||||
* exactly like the plain entry point */
|
||||
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);
|
||||
ret = wc_PufReadSram(&ctx, testSram, sizeof(testSram));
|
||||
if (ret != 0)
|
||||
return WC_TEST_RET_ENC_EC(ret);
|
||||
if (wc_PufReconstructEx(&ctx, helperBuf, WC_PUF_HELPER_BYTES,
|
||||
((word32)WC_PUF_PROFILE_ID) ^ 1u)
|
||||
!= WC_NO_ERR_TRACE(BAD_FUNC_ARG))
|
||||
return WC_TEST_RET_ENC_NC;
|
||||
ret = wc_PufReconstructEx(&ctx, helperBuf, WC_PUF_HELPER_BYTES,
|
||||
(word32)WC_PUF_PROFILE_ID);
|
||||
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);
|
||||
if (XMEMCMP(id1, id2, WC_PUF_ID_SZ) != 0)
|
||||
return WC_TEST_RET_ENC_NC;
|
||||
}
|
||||
|
||||
/* ---- Test 9: BCH encoder known-answer test ---- *
|
||||
* Recover codeword 0 from the enrolled helper data (cw = raw XOR helper,
|
||||
* the helper contributing zero across the message region) and compare it
|
||||
* against a vector produced by the independent python reference encoder
|
||||
* in scripts/puf_bch_genpoly.py. This pins the C encoder itself for the
|
||||
* selected profile - including the non-byte-aligned k=78/50/36 cases -
|
||||
* rather than only the generator polynomial table. */
|
||||
{
|
||||
WOLFSSL_SMALL_STACK_STATIC const byte katCw[WC_PUF_CW_BYTES] = {
|
||||
#if WC_PUF_BCH_T == 7
|
||||
0x0D, 0xBC, 0x4B, 0x1A, 0x89, 0x78, 0xC7, 0xA6,
|
||||
0x05, 0xA5, 0x88, 0xBD, 0x7E, 0xB2, 0x94, 0x5E
|
||||
#elif WC_PUF_BCH_T == 10
|
||||
0x0D, 0xBC, 0x4B, 0x1A, 0x89, 0x78, 0xC7, 0xA6,
|
||||
0xB6, 0x86, 0xF2, 0xC2, 0xE4, 0x10, 0x37, 0x3A
|
||||
#elif WC_PUF_BCH_T == 13
|
||||
0x0D, 0xBC, 0x4B, 0x1A, 0x89, 0x78, 0xD0, 0xC9,
|
||||
0x4D, 0x15, 0x71, 0x8E, 0xEA, 0x96, 0x61, 0x4E
|
||||
#elif WC_PUF_BCH_T == 15
|
||||
0x0D, 0xBC, 0x4B, 0x1A, 0x8B, 0x1A, 0xD8, 0x4F,
|
||||
0x92, 0x24, 0xB1, 0x2D, 0x79, 0x39, 0x09, 0x40
|
||||
#endif
|
||||
};
|
||||
byte gotCw[WC_PUF_CW_BYTES];
|
||||
byte rawBit, helBit;
|
||||
int j, hb;
|
||||
|
||||
XMEMSET(gotCw, 0, sizeof(gotCw));
|
||||
for (j = 0; j < WC_PUF_BCH_N; j++) {
|
||||
rawBit = (testSram[j / 8] >> (7 - (j % 8))) & 1;
|
||||
helBit = 0;
|
||||
if (j >= WC_PUF_HELPER_OFF) {
|
||||
hb = j - WC_PUF_HELPER_OFF;
|
||||
helBit = (helperBuf[hb / 8] >> (7 - (hb % 8))) & 1;
|
||||
}
|
||||
if ((rawBit ^ helBit) != 0)
|
||||
gotCw[j / 8] |= (byte)(1 << (7 - (j % 8)));
|
||||
}
|
||||
if (XMEMCMP(gotCw, katCw, WC_PUF_CW_BYTES) != 0)
|
||||
return WC_TEST_RET_ENC_NC;
|
||||
}
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return 0;
|
||||
|
||||
@@ -24,9 +24,28 @@
|
||||
\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.
|
||||
memory using a configurable BCH(127,k,t) fuzzy extractor over GF(2^7) with
|
||||
HKDF key derivation.
|
||||
|
||||
Build: ./configure --enable-puf (auto-enables HKDF)
|
||||
Configuration (override in user_settings.h or via CPPFLAGS):
|
||||
- WC_PUF_BCH_T selects the error-correction strength / BCH profile:
|
||||
t=7 (k=78), t=10 (k=64, default), t=13 (k=50), t=15 (k=36). Higher t
|
||||
corrects more bit flips per 127-bit codeword at the cost of entropy.
|
||||
- WC_PUF_NUM_CODEWORDS (default 16) trades raw SRAM footprint and helper
|
||||
data size against total derived-key entropy.
|
||||
|
||||
Enrollment and reconstruction MUST use identical WC_PUF_BCH_T and
|
||||
WC_PUF_NUM_CODEWORDS (and the same hash); a mismatch silently produces a
|
||||
wrong key. See WC_PUF_PROFILE_ID and wc_PufGetParams().
|
||||
|
||||
WC_PUF_HELPER_COMPACT (opt-in) stores only the parity bits of the helper
|
||||
data, shrinking it to 39-72% of the default size. It changes the stored
|
||||
format, so the default keeps the layout used by wolfSSL 5.9.2.
|
||||
|
||||
Build: ./configure --enable-puf[=small|balanced|strong|strongest]
|
||||
(auto-enables HKDF). CMake: -DWOLFSSL_PUF=yes with
|
||||
-DWOLFSSL_PUF_PROFILE=small|balanced|strong|strongest and
|
||||
-DWOLFSSL_PUF_NUM_CODEWORDS=<n>.
|
||||
|
||||
For a bare-metal example (tested on NUCLEO-H563ZI), see:
|
||||
https://github.com/wolfSSL/wolfssl-examples/tree/master/puf
|
||||
@@ -52,29 +71,132 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* BCH(127,64,t=10) parameters */
|
||||
/* ------------------------------------------------------------------------ *
|
||||
* Configuration knobs (override in user_settings.h or via CPPFLAGS). *
|
||||
* *
|
||||
* WC_PUF_NUM_CODEWORDS: number of BCH codewords. Trades SRAM footprint and *
|
||||
* helper-data size against derived-key entropy. Default 16. *
|
||||
* WC_PUF_BCH_T: BCH error-correction capability (bit flips corrected per *
|
||||
* 127-bit codeword). Selects a BCH(127,k,t) profile. Supported values: *
|
||||
* t=7 (k=78) smaller parity, high entropy, less correction *
|
||||
* t=10 (k=64) default, wire-compatible with prior releases *
|
||||
* t=13 (k=50) stronger correction for noisier SRAM *
|
||||
* t=15 (k=36) strongest supported *
|
||||
* *
|
||||
* IMPORTANT: enrollment and reconstruction MUST use identical *
|
||||
* WC_PUF_BCH_T and WC_PUF_NUM_CODEWORDS (and the same hash). A mismatch *
|
||||
* silently produces a wrong key. Persist WC_PUF_PROFILE_ID (or the values *
|
||||
* from wc_PufGetParams) with the helper data and compare on reconstruct. *
|
||||
* ------------------------------------------------------------------------ */
|
||||
#ifndef WC_PUF_NUM_CODEWORDS
|
||||
#define WC_PUF_NUM_CODEWORDS 16
|
||||
#endif
|
||||
#ifndef WC_PUF_BCH_T
|
||||
#define WC_PUF_BCH_T 10
|
||||
#endif
|
||||
|
||||
/* Fixed field: GF(2^7), codeword length n = 127 */
|
||||
#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 */
|
||||
/* Profile ladder: WC_PUF_BCH_T -> message length k. Only valid narrow-sense
|
||||
* BCH(127) designed-distance profiles are accepted; other t values are a
|
||||
* compile error rather than a silently wrong code. */
|
||||
#if WC_PUF_BCH_T == 7
|
||||
#define WC_PUF_BCH_K 78
|
||||
#elif WC_PUF_BCH_T == 10
|
||||
#define WC_PUF_BCH_K 64
|
||||
#elif WC_PUF_BCH_T == 13
|
||||
#define WC_PUF_BCH_K 50
|
||||
#elif WC_PUF_BCH_T == 15
|
||||
#define WC_PUF_BCH_K 36
|
||||
#else
|
||||
#error "Unsupported WC_PUF_BCH_T; use 7, 10, 13, or 15 (see puf.h)"
|
||||
#endif
|
||||
|
||||
/* 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 */
|
||||
/* Generator-polynomial degree = parity bits per codeword = n - k */
|
||||
#define WC_PUF_BCH_DEG (WC_PUF_BCH_N - WC_PUF_BCH_K)
|
||||
|
||||
/* Output key size */
|
||||
#if WC_PUF_NUM_CODEWORDS < 1
|
||||
#error "WC_PUF_NUM_CODEWORDS must be >= 1"
|
||||
#endif
|
||||
/* WC_PUF_PROFILE_ID packs the codeword count into a 12-bit field; bound it so
|
||||
* the fingerprint stays unique (and representable) for every valid build. Note
|
||||
* this is a field-width limit, not a recommendation: wc_PufCtx embeds rawSram,
|
||||
* helperData and stableBits by value, so it grows ~30 bytes per codeword and
|
||||
* large counts are impractical on the small-RAM targets this module targets. */
|
||||
#if WC_PUF_NUM_CODEWORDS > 4095
|
||||
#error "WC_PUF_NUM_CODEWORDS must be <= 4095 (WC_PUF_PROFILE_ID field)"
|
||||
#endif
|
||||
|
||||
/* Per-codeword byte sizes */
|
||||
#define WC_PUF_CW_BYTES ((WC_PUF_BCH_N + 7) / 8) /* 16 for n=127 */
|
||||
#define WC_PUF_MSG_BYTES ((WC_PUF_BCH_K + 7) / 8)
|
||||
#define WC_PUF_PARITY_BYTES ((WC_PUF_BCH_DEG + 7) / 8)
|
||||
|
||||
/* Raw SRAM readout: 128-bit stride per codeword (n=127 fits in 128 bits) */
|
||||
#define WC_PUF_RAW_BITS (WC_PUF_NUM_CODEWORDS * 128)
|
||||
#define WC_PUF_RAW_BYTES (WC_PUF_RAW_BITS / 8)
|
||||
|
||||
/* Reconstructed stable bits: k message bits per codeword, bit-packed */
|
||||
#define WC_PUF_STABLE_BITS (WC_PUF_NUM_CODEWORDS * WC_PUF_BCH_K)
|
||||
#define WC_PUF_STABLE_BYTES ((WC_PUF_STABLE_BITS + 7) / 8)
|
||||
|
||||
/* Helper data, bit-packed. Only the (n - k) parity bits per codeword carry
|
||||
* information: helper = raw XOR encoded-codeword, and the systematic message
|
||||
* part of the codeword is a verbatim copy of the raw bits it came from, so the
|
||||
* leading k bits are identically zero. Size OTP/flash for that, not for n.
|
||||
*
|
||||
* WC_PUF_HELPER_COMPACT stores only the parity region. It changes the stored
|
||||
* format, so it is opt-in: the default layout stays compatible with helper
|
||||
* data enrolled by wolfSSL 5.9.2. The choice is encoded in
|
||||
* WC_PUF_PROFILE_ID so a mismatch is detectable. */
|
||||
#ifdef WC_PUF_HELPER_COMPACT
|
||||
#define WC_PUF_HELPER_OFF WC_PUF_BCH_K /* first codeword bit stored */
|
||||
#define WC_PUF_HELPER_LEN WC_PUF_BCH_DEG /* bits stored per codeword */
|
||||
#define WC_PUF_HELPER_ID 1
|
||||
#else
|
||||
#define WC_PUF_HELPER_OFF 0
|
||||
#define WC_PUF_HELPER_LEN WC_PUF_BCH_N
|
||||
#define WC_PUF_HELPER_ID 0
|
||||
#endif
|
||||
#define WC_PUF_HELPER_BITS (WC_PUF_NUM_CODEWORDS * WC_PUF_HELPER_LEN)
|
||||
#define WC_PUF_HELPER_BYTES ((WC_PUF_HELPER_BITS + 7) / 8)
|
||||
|
||||
/* Recommended/default derived key size (HKDF output length is selectable) */
|
||||
#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
|
||||
|
||||
/* Hash-selection bit for the profile fingerprint (0 = SHA-256, 1 = SHA3-256).
|
||||
* A device enrolled and reconstructed with different hashes yields a silently
|
||||
* wrong key, so the hash must be part of the fingerprint. */
|
||||
#ifdef WC_PUF_SHA3
|
||||
#define WC_PUF_HASH_ID 1
|
||||
#else
|
||||
#define WC_PUF_HASH_ID 0
|
||||
#endif
|
||||
|
||||
/* Compact profile fingerprint the application can persist next to its helper
|
||||
* data and compare before reconstruction to detect a build mismatch. Packs
|
||||
* (hash, helper format, m, t, num_codewords); n is fixed at 127 and k is
|
||||
* fully determined by t, so neither needs its own field. Layout:
|
||||
* bit 31 hash select (0 = SHA-256, 1 = SHA3-256)
|
||||
* bit 30 helper format (0 = full codeword, 1 = compact parity-only)
|
||||
* bits 27-29 m (GF exponent)
|
||||
* bits 19-26 t (error-correction capability)
|
||||
* bits 12-18 reserved
|
||||
* bits 0-11 codeword count
|
||||
* This macro necessarily reflects the INCLUDING APPLICATION's build; use
|
||||
* wc_PufGetProfileId() to read what the library itself was compiled with. */
|
||||
#define WC_PUF_PROFILE_ID \
|
||||
(((word32)WC_PUF_HASH_ID << 31) | \
|
||||
((word32)WC_PUF_HELPER_ID << 30) | \
|
||||
((word32)WC_PUF_BCH_M << 27) | \
|
||||
((word32)WC_PUF_BCH_T << 19) | \
|
||||
((word32)(WC_PUF_NUM_CODEWORDS) & 0xFFFU))
|
||||
|
||||
/* Flags for wc_PufCtx.flags */
|
||||
#define WC_PUF_FLAG_ENROLLED 0x01
|
||||
#define WC_PUF_FLAG_READY 0x02
|
||||
@@ -101,6 +223,13 @@ WOLFSSL_API int wc_PufReconstruct(wc_PufCtx* ctx, const byte* helperData,
|
||||
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_PufGetParams(int* m, int* n, int* k, int* t,
|
||||
int* numCodewords);
|
||||
WOLFSSL_API word32 wc_PufGetProfileId(void);
|
||||
WOLFSSL_API int wc_PufGetHelperData(wc_PufCtx* ctx, byte* helper,
|
||||
word32 helperSz);
|
||||
WOLFSSL_API int wc_PufReconstructEx(wc_PufCtx* ctx, const byte* helperData,
|
||||
word32 helperSz, word32 profileId);
|
||||
WOLFSSL_API int wc_PufZeroize(wc_PufCtx* ctx);
|
||||
|
||||
#ifdef WOLFSSL_PUF_TEST
|
||||
|
||||
Reference in New Issue
Block a user