Support hardware PUF on lpc55s69 - #10584
Conversation
|
- reworked activationCode and added to api - updated test.c - improved error checking - reworked device id usage - more
dgarske
left a comment
There was a problem hiding this comment.
Skoll Code Review
Scan type: reviewOverall recommendation: REQUEST_CHANGES
Findings: 12 total — 12 posted, 0 skipped
12 finding(s) posted as inline comments (see file-level comments below)
Posted findings
- [High] wc_HWPUF_Register reads uninitialized hwpuf-registered before zeroing —
wolfcrypt/src/hwpuf.c:42-65 - [Medium] Unused static function getACFromPFR (dead code, uses memset) —
wolfcrypt/src/port/nxp/hwpuf_port.c:54-65 - [Medium] nxp_hwpuf_SetKey has multiple unused parameters —
wolfcrypt/src/port/nxp/hwpuf_port.c:185-195 - [Medium] Callback parameter ctx shadows file-scope global ctx and is unused —
wolfcrypt/src/port/nxp/hwpuf_port.c:258 - [Medium] RNG hardware register read without ensuring RNG is initialized —
wolfcrypt/src/port/nxp/hwpuf_port.c:96 - [Low] Signed/unsigned comparison in hwpuf_test loop —
wolfcrypt/test/test.c:23443-23444 - [Low] WOLFSSL_NXP_HWPUF does not imply WOLFSSL_HWPUF —
wolfssl/wolfcrypt/settings.h:2207-2209 - [Low] Inconsistent key-index bound: literal 16 vs kPUF_KeyIndexMax —
wolfcrypt/src/port/nxp/hwpuf_port.c:74-79 - [Low] wc_CryptoInfo.hwpuf.hwpuf typed as void loses type safety* —
wolfssl/wolfcrypt/cryptocb.h:582 - [Low] HWPUF_DEINIT_E error code defined but never returned —
wolfssl/wolfcrypt/error-crypt.h:332 - [Low] No test coverage for SetKey path or double-register —
wolfcrypt/test/test.c:23341-23502 - [Info] hwpuf_test step numbering skips 'Test 6' —
wolfcrypt/test/test.c:23407-23434
Review generated by Skoll
|
Jenkins retest this please. History lost |
dgarske
left a comment
There was a problem hiding this comment.
Skoll Multi-Scan Review
Modes: review + review-securityOverall recommendation: COMMENT
Findings: 14 total — 14 posted, 0 skipped
11 finding(s) posted as inline comments (see file-level comments below)
3 finding(s) not tied to a diff line (full detail below)
Posted findings
- [Medium] [review] Bad-arg / error-path test coverage gaps for new HWPUF API —
wolfcrypt/test/test.c:23450-23482 - [Low] [review+review-security] Large stack frame in hwpuf_test on embedded target (no WOLFSSL_SMALL_STACK) —
wolfcrypt/test/test.c:23340-23360 - [Low] [review-security] Retrieved key buffer not zeroized on PUF_GetKey failure path —
wolfcrypt/src/port/nxp/hwpuf_port.c:221-225 - [Low] [review-security] ctx keyMask wiped with XMEMSET instead of ForceZero in Enroll —
wolfcrypt/src/port/nxp/hwpuf_port.c:133-134 - [Low] [review-security] hw-bus GetKey path zeroes caller buffer using unvalidated keySz —
wolfcrypt/src/port/nxp/hwpuf_port.c:208-220 - [Low] [review-security] GetHwpufTypeStr may return NULL passed to printf %s —
wolfcrypt/src/cryptocb.c:250-271,373-378 - [Info] [review] Missing space before '=' in HWPUF_GENERATE_KEY_E enumerator —
wolfssl/wolfcrypt/error-crypt.h:332 - [Info] [review] keyCodeCheck custom return compared against NXP kStatus_Success —
wolfcrypt/src/port/nxp/hwpuf_port.c:63-78,201-203 - [Info] [review] New brace-scope created in test just to declare a loop index —
wolfcrypt/test/test.c:23442-23448 - [Info] [review-security] Misleading test comment references wrong variable —
wolfcrypt/test/test.c:23460-23467 - [Info] [review] wc_CryptoInfo.hwpuf.ctx field declared but never populated —
wolfssl/wolfcrypt/cryptocb.h:585
Findings not tied to a diff line
Singleton global state conflicts with per-context wc_HWPUF* API
File: wolfcrypt/src/hwpuf.c:41; wolfcrypt/src/port/nxp/hwpuf_port.c:56-57,80
Function: wc_HWPUF_Register / wc_HWPUF_Unregister / nxp_hwpuf_*
Severity: Medium
Category: api
The public API is shaped around a per-instance wc_HWPUF* context, but registration/device state is held in file-scope statics: hwpuf_registered in hwpuf.c, and ctx, conf, nxp_rng_initialized in the NXP port. This makes the implementation effectively a singleton. Two consequences: (1) wc_HWPUF_Register on a second context returns HWPUF_REGISTER_E even though it is a distinct object; (2) wc_HWPUF_Unregister(ctxB) while ctxA is the registered context still passes the if (!hwpuf_registered) guard (the global is 1) and calls nxp_hwpuf_UnregisterDevice(ctxB), unregistering ctxB->devId -- which for a never-registered/zeroed context is 0/garbage, potentially tearing down the wrong device. The PUF peripheral is genuinely a singleton so static ctx/conf is defensible, but the pointer-based API invites misuse.
Recommendation: Either document the singleton restriction explicitly in the public header, or guard Unregister against tearing down a device for a context that was never the registered one (e.g. match a stored devId/pointer before unregistering).
Referenced code: wolfcrypt/src/hwpuf.c:41; wolfcrypt/src/port/nxp/hwpuf_port.c:56-57,80 (8 lines)
Public key-code-size macro hardcoded, decoupled from NXP SDK formula
File: wolfssl/wolfcrypt/hwpuf.h:37-38; wolfcrypt/src/port/nxp/hwpuf_port.c:175,205
Function: HWPUF_KEY_SIZE_TO_KEY_CODE_SIZE / nxp_hwpuf_GenerateKey / nxp_hwpuf_GetKey
Severity: Low
Category: question
The public macro HWPUF_KEY_SIZE_TO_KEY_CODE_SIZE(keysz) always expands to the literal 52 (ignoring its argument), and callers size their key-code buffers from it. The port, however, validates buffer sizes against the NXP SDK macro PUF_GET_KEY_CODE_SIZE_FOR_KEY_SIZE(keySz) (kcSz != keyCodeSz -> BAD_FUNC_ARG). If a future NXP SDK changes its key-code size formula, the public macro and the port's validation will silently diverge and every GenerateKey/GetKey call will fail with BAD_FUNC_ARG. Deriving the public macro from the SDK macro (or adding a compile-time assertion that they agree) would make the coupling explicit.
Recommendation: Define HWPUF_KEY_SIZE_TO_KEY_CODE_SIZE in terms of PUF_GET_KEY_CODE_SIZE_FOR_KEY_SIZE where possible, or add a static assert that the two agree for 16/24/32-byte keys so they cannot drift apart.
Referenced code: wolfssl/wolfcrypt/hwpuf.h:37-38; wolfcrypt/src/port/nxp/hwpuf_port.c:175,205 (5 lines)
Function-like macros missing parentheses around parameter
File: wolfssl/wolfcrypt/port/nxp/hwpuf_port.h:32-33; wolfssl/wolfcrypt/hwpuf.h:38
Function: HWPUF_KEY_SIZE_IS_VALID / HWPUF_KEY_SIZE_TO_KEY_CODE_SIZE
Severity: Info
Category: style
HWPUF_KEY_SIZE_IS_VALID(keysz) expands keysz without wrapping it in parentheses ((keysz == 16 || keysz == 24 || keysz == 32)); a caller passing an expression rather than a plain variable could hit operator-precedence surprises. (Current callers pass simple vars so it is harmless today.) Separately, HWPUF_KEY_SIZE_TO_KEY_CODE_SIZE(keysz) discards its argument entirely (always 52), which is intentional but worth a comment so readers do not assume it scales with key size.
Recommendation: Parenthesize the macro parameter in HWPUF_KEY_SIZE_IS_VALID: ((keysz) == 16 || (keysz) == 24 || (keysz) == 32); keep a comment noting the key-code size is fixed at 52.
Referenced code: wolfssl/wolfcrypt/port/nxp/hwpuf_port.h:32-33; wolfssl/wolfcrypt/hwpuf.h:38-41 (4 lines)
Review generated by Skoll
| } | ||
| } | ||
|
|
||
| /* ---- Test 7: Bad argument checks ---- */ |
There was a problem hiding this comment.
🟠 [Medium] Bad-arg / error-path test coverage gaps for new HWPUF API · test
Test 7 exercises NULL/bad args for wc_HWPUF_Init, Deinit, Enroll, Zeroize, and a couple of GenerateKey/GetKey cases, but several new public entry points have no NULL/error-path coverage: wc_HWPUF_Start(NULL,...), wc_HWPUF_GetKey(NULL,...), wc_HWPUF_GenerateKey(NULL,...), wc_HWPUF_Unregister(NULL), and wc_HWPUF_Register(NULL,...). The state-machine error returns are also untested: wc_HWPUF_Start/Enroll before Init (expect HWPUF_INIT_E), wc_HWPUF_GenerateKey/GetKey before Start (expect HWPUF_START_E), and Enroll when already enrolled (expect HWPUF_ENROLL_E). These are the gating branches the wrapper adds on top of the port, so they are worth pinning.
Fix: Extend hwpuf_test to cover the remaining NULL-argument paths and the wrapper's state-machine error returns (Start before Init -> HWPUF_INIT_E, GetKey before Start -> HWPUF_START_E, double Enroll -> HWPUF_ENROLL_E).
| } | ||
| #endif /* WOLFSSL_PUF */ | ||
|
|
||
| #ifdef WOLFSSL_HWPUF |
There was a problem hiding this comment.
🔵 [Low] Large stack frame in hwpuf_test on embedded target (no WOLFSSL_SMALL_STACK) · Resource Leak
activationCode[HWPUF_ACTIVATION_CODE_SIZE] is 1192 bytes, and together with the three 52-byte key-code buffers and six key buffers the function consumes roughly 1.4-1.5 KB of stack. This test only builds/runs on the LPC55S69 (WOLFSSL_HWPUF + WOLFSSL_NXP_HWPUF), an embedded MCU with a small stack, so it risks stack exhaustion on the very target it is meant to run on. wolfSSL convention for stack buffers larger than ~100 bytes is the WOLFSSL_SMALL_STACK (heap) pattern. (Both reviewers flagged this; review rated it Low, review-security rated it Info -- stricter Low retained.)
Fix: Allocate activationCode (and optionally the key/keycode buffers) from the heap under the WOLFSSL_SMALL_STACK pattern, or document the stack requirement, to avoid a ~1.5KB stack frame on the MCU.
| if (key) | ||
| XMEMSET(key, 0, keySz); /* no key to return, zero out */ | ||
| } | ||
| else { |
There was a problem hiding this comment.
🔵 [Low] Retrieved key buffer not zeroized on PUF_GetKey failure path · Zeroization
In the non-hw-bus branch of nxp_hwpuf_GetKey, PUF_GetKey() writes derived key material into the caller's key buffer. If the vendor call returns a non-success status after partially populating key, the function returns HWPUF_GET_KEY_E without zeroizing key. Sensitive key material may be left in the output buffer on an error return. The wolfSSL convention (and the project rules) is to ForceZero() secret buffers on every return path, including error paths. The success path is fine; the gap is only on the error path.
Fix: On the PUF_GetKey() != kStatus_Success path, call ForceZero(key, keySz) (when key != NULL) before returning HWPUF_GET_KEY_E.
| return HWPUF_ENROLL_E; | ||
| } | ||
|
|
||
| /* wipe ctx if enroll succeeded (re-enroll will render ctx moot) */ |
There was a problem hiding this comment.
🔵 [Low] ctx keyMask wiped with XMEMSET instead of ForceZero in Enroll · Zeroization
nxp_hwpuf_Enroll clears the file-static ctx (which holds keyMask, the random mask used by PUF_GetHwKey to obscure key material on the HW bus) using XMEMSET(&ctx, 0, sizeof(ctx)). The companion nxp_hwpuf_Zeroize correctly uses ForceZero(&ctx, sizeof(ctx)) for the same structure. A plain XMEMSET can be elided by the optimizer for data the compiler believes is dead, so a sensitive mask value could persist. This is an inconsistency with the rest of the file and with the project rule to zero secrets with ForceZero().
Fix: Use ForceZero(&ctx, sizeof(ctx)); here for consistency with nxp_hwpuf_Zeroize and to guarantee the wipe is not optimized away.
| kcSz = PUF_GET_KEY_CODE_SIZE_FOR_KEY_SIZE(keysize); | ||
| if (kcSz != keyCodeSz) | ||
| return BAD_FUNC_ARG; | ||
| if (keyidx != kPUF_KeyIndex_00 && (key == NULL || keysize != keySz)) |
There was a problem hiding this comment.
🔵 [Low] hw-bus GetKey path zeroes caller buffer using unvalidated keySz · Logic
For the normal (non-hw-bus) path, nxp_hwpuf_GetKey validates key != NULL && keysize == keySz before writing, where keysize is derived from the validated key code. For the hw-bus path (keyidx == kPUF_KeyIndex_00), that validation is intentionally skipped, but the code then executes XMEMSET(key, 0, keySz) using the caller-supplied keySz without bounding it against the derived keysize. If a caller passes a key buffer smaller than the keySz it reports (a caller-side mistake, not network input), this is an out-of-bounds write. The non-hw path enforces an equality check that prevents this; the hw path has no equivalent guard, an asymmetry worth closing for robustness.
Fix: For the hw-bus path, validate keySz against the derived keysize (e.g. require keySz == keysize when key != NULL) before the XMEMSET, matching the guarantee the non-hw-bus path provides.
| WC_SPAN2_LAST_E = -1018, /* Update to indicate last used error code */ | ||
| WC_LAST_E = -1018, /* the last code used either here or in | ||
| HWPUF_REGISTER_E = -1019, /* HWPUF registration failed */ | ||
| HWPUF_INIT_E = -1020, /* HWPUF initialization failed */ |
There was a problem hiding this comment.
⚪ [Info] Missing space before '=' in HWPUF_GENERATE_KEY_E enumerator · style
HWPUF_GENERATE_KEY_E= -1023 is missing the space before = that every other enumerator in the block uses, and it breaks the value-column alignment of the surrounding HWPUF_*_E entries.
Fix: Add the missing space and align the = with the neighboring enumerators: HWPUF_GENERATE_KEY_E = -1023,.
| if (keyCode == NULL || keyCodeSz < PUF_MIN_KEY_CODE_SIZE) | ||
| return BAD_FUNC_ARG; | ||
|
|
||
| ret = keyCodeCheck(keyCode, &keytype, &keyidx, &keysize); |
There was a problem hiding this comment.
⚪ [Info] keyCodeCheck custom return compared against NXP kStatus_Success · style
keyCodeCheck returns its own codes 0/1/2/3, but the caller checks if (ret != kStatus_Success), mixing a local convention with the NXP status enum. It happens to work only because kStatus_Success is 0. This is misleading to readers who would expect kStatus_Success only to be paired with NXP SDK calls.
Fix: Compare keyCodeCheck's result against literal 0 (or give it named return codes) rather than the NXP kStatus_Success enum.
| ret = wc_HWPUF_GetKey(&hwpuf, keyCode32, sizeof(keyCode32), key32_2, sizeof(key32_2)); | ||
| if (ret != 0) | ||
| return WC_TEST_RET_ENC_EC(ret); | ||
| { /* key1 should be zeroed */ |
There was a problem hiding this comment.
⚪ [Info] New brace-scope created in test just to declare a loop index · convention
A { word32 idx; for (...) ... } block is introduced solely to declare idx. wolfSSL C89 style declares variables at the top of the function or an existing block rather than opening a new scope just to declare. idx could be declared with the other locals at the top of hwpuf_test.
Fix: Move the loop-index declaration to the function's top-of-block declarations to match wolfSSL C89 style and drop the extra brace scope.
| return WC_TEST_RET_ENC_NC; | ||
| if (wc_HWPUF_Zeroize(NULL) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) | ||
| return WC_TEST_RET_ENC_NC; | ||
| /* out of bounds key index */ |
There was a problem hiding this comment.
⚪ [Info] Misleading test comment references wrong variable · Logic
In Test 6 (hw-bus key), the comment says /* key1 should be zeroed */ but the loop actually validates key32_2. There is no key1 variable. Cosmetic only; no behavioral impact.
Fix: Update the comment to reference key32_2 (the buffer actually checked).
| struct { | ||
| wc_HWPUF* hwpuf; /* wc_HWPUF* context */ | ||
| int type; /* enum wc_HwpufType - discriminator */ | ||
| const void* ctx; /* read-only caller context */ |
There was a problem hiding this comment.
⚪ [Info] wc_CryptoInfo.hwpuf.ctx field declared but never populated · style
The hwpuf cryptoinfo struct declares const void* ctx; /* read-only caller context */, but none of the wc_CryptoCb_Hwpuf* setters in cryptocb.c ever assign cryptoInfo.hwpuf.ctx, and the NXP port reads devCtx (the registered callback ctx), not info->hwpuf.ctx. The field is currently dead. Either wire it up (set it from a caller-supplied context) or drop it to avoid implying a contract that is not honored.
Fix: Drop the unused ctx member, or populate it in the wc_CryptoCb_Hwpuf* wrappers if caller context is intended to be passed through, so the struct reflects real behavior.
This adds hardware puf support to wolfCrypt...
An effort has been made to be as compatible with the sw puf (WOLFSSL_PUF) implementation as possible, but the two fundamentally diverge in certain respects.
Not all features of the hw puf are supported yet. Specifically, this initial implementation supports...
Testing
Tested with new test functionality added to wolfcrypt/test/test.c
Checklist