Skip to content

Fix/cvc explicit ec params - #142

Open
lrascao wants to merge 2 commits into
polhenarejos:masterfrom
lrascao:fix/cvc-explicit-ec-params
Open

lrascao wants to merge 2 commits into
polhenarejos:masterfrom
lrascao:fix/cvc-explicit-ec-params

Conversation

@lrascao

@lrascao lrascao commented Sep 17, 2026

Copy link
Copy Markdown

Summary

Bug fix.

EXTERNAL AUTHENTICATE always fails with 6985 for any key generated through
OpenSC, which makes the M-of-N public key authentication already present in
this tree unusable with that toolchain.

cvc_inherite_ec_group() resolves EC domain parameters by walking up to a
self-signed CVC even when the certificate in hand already carries explicit
parameters (tag 0x81). When the CA named in the CAR has no certificate in
the puk store, the walk sets ca_data = NULL, cvc_get_pub() returns NULL,
the curve resolves to MBEDTLS_ECP_DP_NONE, and puk_verify() rejects every
signature.

That is the case for every key generated through OpenSC: pkcs15init stamps a
fixed placeholder issuer into the key's CVC (pkcs15-sc-hsm.c,
car = "UTCA00001") for which no certificate exists anywhere.

The fix uses the certificate's own parameters when present, and walks only
when they are absent and genuinely have to be inherited.

Details / Impact

  • Hardware / board(s) tested: Pico 2 / RP2350 as the target; a second
    device on 6.4 as the custodian.
  • Firmware / commit/base version: fa3fd433 (6.6 tree).
  • Security impact: narrows rather than widens. All three callers pass the
    certificate whose own public key they are about to interpret — cmd_pso.c
    reads tag 0x86 with the returned curve, cvc.c:440/522 verify with the
    signer's key — so consulting that certificate's own parameters is also more
    correct than inheriting: a certificate whose explicit parameters differ from
    its CA's previously resolved to the CA's curve. No change to PIN handling,
    key storage, or attestation.
  • Behavior changes: no new commands or API surface.
    cvc_inherite_ec_group() returns a curve in cases where it previously
    returned DP_NONE; the inheritance path is unchanged.

Testing

Same device, same commit, same registered key, same custodian; only the patch
differing:

firmware EXTERNAL AUTHENTICATE
fa3fd433 6985
fa3fd433 + patch 9000, card reports authenticated=1

Reversible in both directions (patched → stock → patched), so the result is
not an artefact of one flash.

Steps: generate an EC key on the custodian via pkcs11-tool, export with
sc-hsm-tool -e, register the request on a target initialised with
-K 1 -n 1, then run MSE 00 22 81 A4 (tag 83 = CHR) → GET CHALLENGE
sign SHA-256(dev_name || challenge) on the custodian → EXTERNAL AUTHENTICATE with raw r||s.

The second commit adds a unit test over cvc_inherite_ec_group() with vectors
taken from a live device. The regression case fails without the patch and
passes with it; three further cases covering inheritance via the puk store,
inheritance with no CA available, and the self-signed path pass either way,
which is what shows inheritance behaviour is unchanged. ctest 4/4.

Anything else?

Found while implementing the PKA challenge-response over PC/SC, which OpenSC
does not provide (OpenSC/OpenSC#594).

No overlap with #137 — that changes the dev_name fallback in
reset_puk_store(); this changes curve resolution in cvc.c.

cvc_inherite_ec_group() resolves the curve by walking up to a self-signed
CVC, even when the certificate in hand already carries explicit domain
parameters (tag 0x81). When the CA named in the CAR has no certificate in
the puk store, the walk sets ca_data = NULL, cvc_get_pub() returns NULL and
the curve resolves to MBEDTLS_ECP_DP_NONE.

That is the case for every key generated through OpenSC: pkcs15init stamps a
fixed placeholder issuer into the key's CVC (src/pkcs15init/pkcs15-sc-hsm.c,
car = "UTCA00001") for which no certificate exists anywhere. puk_verify()
therefore fails and cmd_external_authenticate() rejects every signature with
6985, making n-of-m public key authentication unusable with OpenSC-generated
keys.

All three callers pass the certificate whose own public key they are about to
interpret -- cmd_pso.c then reads tag 0x86 using the returned curve, and
cvc.c:440/522 verify with the signer's key -- so reading the certificate's own
0x81 is also more correct than inheriting: a certificate whose explicit
parameters differ from its CA's previously resolved to the wrong curve. The
walk is retained unchanged for certificates that genuinely omit them.

Measured on Pico 2 / RP2350 at fa3fd43, same device, same registered key,
only this patch differing:

  6.6 stock    EXTERNAL AUTHENTICATE -> 6985
  6.6 + patch  EXTERNAL AUTHENTICATE -> 9000, card reports authenticated=1
Four cases against vectors taken from a live device: an EC key generated
through OpenSC (CHR=ESPICOHSMTR00001, CAR=UTCA00001), the same certificate
with its domain parameters removed, a self-signed variant, and a CA for the
inherited case to resolve against.

The regression case -- explicit parameters with an unresolvable CAR -- fails
before a0e0a15 and passes after. The other three pass either way, which is
what demonstrates that consulting a certificate's own parameters does not
change inheritance behaviour.

The puk store is stubbed so both branches are reachable deterministically;
the remaining externals belong to certificate-writing paths this test does
not exercise.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant