Conversation
|
End-to-end confirmation on hardware: with this patch a card that has been fully erased provisions and completes a key wrap/unwrap round trip, which was impossible before. Sequence on a card after a full 16 MB erase, single INITIALIZE on the patched firmware: Instrumenting Unrelated defect found while testing, reported here only so it is not mistaken for a regression from this patch:
So this looks like a response-path/chaining problem for responses beyond the short-APDU limit, independent of the deadlock this PR fixes. I have not root-caused it and am not proposing a change for it here. It also plausibly explains older reports of Happy to open a separate issue with the trace if useful. |
|
Correction to my note above about I described it as the response deterministically never reaching the host. That was overstated. With more runs the behaviour is intermittent:
What is solid:
So it is an intermittent failure on the response path for a >256-byte response, trigger not yet identified. I have not root-caused it and I am not proposing a change for it here — flagging it only so it is not mistaken for a regression from this PR, and correcting my earlier overstatement. This does not affect the fix in this PR, which is verified independently: two runs from a full 16 MB erase both reach |
|
Retracting the My test build carried an out-of-tree core1 "dead-man's switch" in
That is exactly what happened, and the mechanism is unambiguous:
Both run on core0, so it was a race, which is why it looked intermittent and why I could not find Measured on hardware, same board, same command:
The switch is now removed outright. I did not replace it with anything — the liveness problem it Note the timestamps: I posted the hang report at 23:10 UTC on 2026-08-05 and removed the switch at What this does not change: the instrumented card-side evidence for this PR stands, because it To be precise about which build each earlier measurement came from: the wrap/unwrap round trip I |
|
Validation status, so this is not sitting here unqualified. With this applied (plus the other pico-hsm patch in this pair and pico-keys-sdk#32), my staging Two corrections I have posted elsewhere and want visible here too, since both were mine:
Nothing here needs action — recording it so the state is current. |
Status: still current, and directly exercised on a binary with verified provenanceUnchanged. This is the bootstrap that lets a card with no I corrected a provenance gap on my side since posting this — my superproject pinned On that binary the device-identity path this PR covers is read straight off the card and parsed offline:
RP2350B only; no RP2040 on this bench. |
New evidence: the deadlock scenario reproduced accidentally, and the bootstrap resolved itStill current and unchanged. Since the last update this fix was exercised by the exact failure it While investigating an unrelated filesystem issue (#35) my card reached a state where Recovery required erasing the filesystem region over SWD: That leaves no On the patched build, initialization then succeeded immediately: and the card went straight back to generating keys. The bootstrap path is the only thing that makes So this is no longer only a code-reading argument about a deadlock: a card with a fully erased Nothing else about the PR has changed. |
|
Possibly related with #139 |
…constant 49616b4 fixed the initialization deadlock on a card with no EF.C_DevAut by falling back to a fixed CHR. This keeps that control flow exactly and changes only the value. The fallback CHR is not transient. reset_puk_store() runs from init_sc_hsm() at boot, so on a card with no EF.C_DevAut dev_name is already set to the fallback by the time INITIALIZE builds the device certificate — and that certificate is then stored in EF_TERMCA and read back as dev_name on every subsequent boot. The bootstrap name becomes the card's permanent identity. OpenSC derives the PKCS#11 token serial from that CHR by stripping the last five characters unconditionally: src/libopensc/pkcs15-sc-hsm.c:1448 len = strnlen(devcert.chr, sizeof devcert.chr); /* Strip last 5 digit sequence number from CHR */ len -= 5; So with a fixed "ESPICOHSMTR00001" every device that provisions without a pre-existing EF.C_DevAut reports serial "ESPICOHSMTR" — permanently, and identically. Two such cards on one host cannot be told apart by serial, which is how PKCS#11 tooling selects a token. Using "ESP" + 32 bits of the board serial keeps the recognisable prefix and the 11+5 CVC holder-reference layout, while making the derived serial distinct per device. pico_serial is filled by serial_init() on every supported platform including ESP32, where PICO_UNIQUE_BOARD_ID_SIZE_BYTES is 8, so the last four bytes always exist. Compile-checked: PICO_BOARD=waveshare_rp2350_pizero, arm-gnu-15.2, clean, 0 warnings.
60bfe37 to
591f554
Compare
Yes — related, and your fix supersedes most of this PR. Rebased and cut down to what is left.Sorry for the slow reply. Checked. #139 and this PR are the same deadlock: on a card with no Your 49616b4 fixes it, and I agree with the shape you chose over #139's. Forcing What I have rebased this down to is one value. Not the control flow — that is yours, unchanged if (!dev_name) {
- dev_name = (const uint8_t *) "ESPICOHSMTR00001";
- dev_name_len = (uint16_t)(strlen((const char *)dev_name));
+ dev_name = hsm_bootstrap_dev_name(&dev_name_len);
}The reason it matters is that the fallback is not transient. And OpenSC turns that CHR into the PKCS#11 token serial by stripping the last five characters —
Compile-checked on the rebase: One loose end I deliberately did not touch: |
The red checks are not from this change — fork PRs cannot decrypt the test flash imageFlagging so you do not have to re-derive it.
Same signature on #138 — So nothing in this PR is exercised by those jobs, and nothing in it caused them to fail. It does (If it is worth fixing generally, |
Demonstrated on two physical devices — the claim in this PR is no longer inferred from the codeA second RP2350B board joined the bench today, so the thing this PR asserts can now be shown. The prediction was made before flashing. I read the OTP chip id of the new board over SWD Then a full chip erase ( Both devices, both predictions:
Two SmartCard-HSMs on one host, each selectable by a One incidental finding you may care about more than the aboveSlot ids here are Scope, honestlyRP2350B, two boards, I have not re-measured the stock-firmware arm end to end. I flashed master onto the same board |
Rebased onto master and cut down to one value change. The deadlock this PR originally fixed is
now fixed by your 49616b4 — that part is yours and I have dropped it. What is left is the fallback
CHR itself.
The change
reset_puk_store()keeps exactly the control flow 49616b4 introduced. Only the value changes:where
hsm_bootstrap_dev_name()formats"ESP%02X%02X%02X%02X00001"from the last four bytes ofthe board serial.
Why the value matters
The fallback is not a transient placeholder — it becomes the card's permanent identity:
init_sc_hsm()callsreset_puk_store()at boot, so on a card with noEF.C_DevAut,dev_nameis already the fallback before any command runs;INITIALIZEbuilds the device certificate with that CHR and stores it inEF_TERMCA(
cmd_initialize.c,asn1_cvc_certthenfile_put_data);dev_nameback out of that certificate.So whatever the fallback is on first provisioning, the card keeps forever.
OpenSC derives the PKCS#11 token serial from that CHR by stripping the last five characters
unconditionally —
src/libopensc/pkcs15-sc-hsm.c:1448:With a fixed
ESPICOHSMTR00001, every device provisioned from a blank filesystem reportstoken serial = ESPICOHSMTR. Identically, and permanently. Two such cards on one host cannot bedistinguished by the field PKCS#11 tooling uses to select a token.
With this change the same card reports a serial derived from its own board id — on my bench,
CHR
ESP2202E14A00001->serial num : ESP2202E14A, read back throughpkcs11-tool.Portability
pico_serialis filled byserial_init()on every supported platform including ESP32(
serial.chas anESP_PLATFORMbranch), andPICO_UNIQUE_BOARD_ID_SIZE_BYTESis 8 or 16, so thelast four bytes always exist. The 11+5 CVC holder-reference layout is preserved, as is the
recognisable
ESPprefix.Verification
Compile-checked on this rebase:
PICO_BOARD=waveshare_rp2350_pizero, arm-gnu-15.2 — clean,0 warnings.
stringson the resulting ELF shows the new format string present.Hardware evidence for the mechanism (device-unique CHR surviving provisioning, wrap/unwrap round
trip, RRC posture) is from RP2350B, on this one board. I have no ESP32 hardware, so the ESP32
claim above is source-level only.
Not included, deliberately
cvc.c:43still carriescar = dev_name ? dev_name : "ESPICOHSMTR00001";. As far as I can tellthat branch is now unreachable —
reset_puk_store()guaranteesdev_nameis non-NULL before anycommand runs — so I left it alone to keep this diff to one value. Say the word and I will fold in
its removal, or leave it as the belt-and-braces it now is.
cmd_initialize.c:255's"ESPICOHSMTR"is a PRKD label, not a CHR, and is untouched.