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