SPAKE2+ PSA PAKE API support#810
Conversation
|
Hi @sigvartmh, thank you for your interest in TF-PSA Crypto and for your contribution. Integrating larger contributions like this takes up a significant proportion of our review capacity and we will need to schedule some resources to do so. I will raise this at planning and will let you know once we managed to allocate some capacity for this. |
|
@yanesca I can split this into multiple PRs if that's preferable? It would just be harder to get the CI to run and pass correctly as it would require a lot wiring to error states until implementation is available. |
Yes, that would be very helpful, however I wouldn't like to ask you to put in even more work until I have a reasonable confidence that we can put in some effort on our side.
Yes, that is usually the problem. Once I had a chance to take a closer look we can have a chat and we should be able to come up with a reasonable breakdown that doesn't require a lot of extra/unnecessary work. |
Signed-off-by: Kiruthik Prakash <Kiruthik.Prakash@silabs.com>
Scope the SPAKE2+ public-key import/export work to issue 9342 and make it
build and test standalone on top of development:
- drop the SECP192R1 row from the import curve table (its
MBEDTLS_ECP_DP_SECP192R1 enum is undeclared when the curve is
disabled, which it is by default) and renumber the lookup;
- brace the curve switch case so the local declarations are not
placed directly after a label (-Wc23-extensions);
- add the SPAKE2+ export-size macros (PSA_KEY_EXPORT_SPAKE2P_*_MAX_SIZE)
and wire them into PSA_EXPORT_KEY_OUTPUT_SIZE /
PSA_EXPORT_PUBLIC_KEY_OUTPUT_SIZE so export buffer sizing is correct;
- register PSA_WANT_ALG_SPAKE2P_HMAC and
PSA_WANT_KEY_TYPE_SPAKE2P_PUBLIC_KEY in config-options-current.txt;
- rewrite the negative import cases to use import_with_data /
import_with_policy (the previous import_export_public_key cases
asserted import success and so could not exercise rejection);
- defer the SPAKE2+ KEY_PAIR config selectors and their adjust/check
plumbing to the key-pair issue (9343), and drop unrelated whitespace
reformatting.
Signed-off-by: Sigvart Hovland <sigvart.m@gmail.com>
Replace the family-switch with hardcoded array indices in psa_spake2p_get_curve_from_data_length() with a loop over spake2p_supported_curves[], matching on family and w0_len+L_len. The table becomes the single source of truth, removing the index/renumbering footgun when curve rows are added or removed. Signed-off-by: Sigvart Hovland <sigvart.m@gmail.com>
Extend psa_import_key()/psa_export_key() to handle PSA_KEY_TYPE_SPAKE2P_KEY_PAIR
on top of the public-key support from #9342:
- psa_spake2p_import_key() now parses the prover key pair w0 || w1 (two
equal-length secp256r1/384r1/521r1 scalars) in addition to the verifier
registration record w0 || L; the stored key size is the curve size;
- relax the permitted-algorithm policy check to accept any SPAKE2+ algorithm
or PSA_ALG_NONE;
- enable the SPAKE2+ KEY_PAIR config selectors (BASIC/IMPORT/EXPORT) with the
matching key-pair-type adjustment, prerequisite check, derived
PSA_WANT_ALG_SOME_SPAKE2P aggregate, and config-options registration;
- add import/export round-trip tests (key pair P-256/P-384, public P-256/P-521)
and key-pair negative cases (odd length, unsupported curve family).
Signed-off-by: Sigvart Hovland <sigvart.m@gmail.com>
Replace the switch in psa_spake2p_secp_r1_bits_from_scalar_len() with a lookup over spake2p_supported_curves[] (matching SECP_R1 family and w0_len), so the supported curve set lives in one table shared with the public-key lookup. Signed-off-by: Sigvart Hovland <sigvart.m@gmail.com>
Signed-off-by: Kusumit Ghoderao <kusumitg@gmail.com>
Signed-off-by: Kusumit Ghoderao <kusumitg@gmail.com>
Signed-off-by: Sigvart Hovland <sigvart.m@gmail.com>
Carve the Prover-only slice of SPAKE2+ (RFC 9383) from the full
implementation: psa_pake_setup() now accepts SPAKE2+ key types for the
client (Prover) role, and psa_pake_output() produces the
PSA_PAKE_STEP_KEY_SHARE message.
Components:
- Built-in primitive drivers/builtin/src/spake2p.c (+ spake2p.h and the
test-only spake2p_invasive.h), restricted to init/free/setup,
set_context/user/peer and write_key_share with its M/N constants and
own-share computation. Read-key-share, confirmation, shared-key
derivation, CMAC and the self-test are intentionally left out of this
slice.
- PSA built-in driver (psa_crypto_pake.c): SPAKE2+ setup path and the
KEY_SHARE output arm; abort frees the context.
- PSA core (psa_crypto.c): SPAKE2+ branches in set_password_key,
psa_pake_setup, psa_pake_set_role, psa_pake_complete_inputs and
psa_pake_output, the key-share exchange prologue/epilogue, the
get_context driver helpers, and context cleanup in abort.
- Public API (crypto_extra.h): SPAKE2+ driver steps, the computation
stage, PSA_PAKE_OUTPUT/INPUT_SIZE key-share sizing and the 133-byte
max buffers, and the get_context declarations.
- Config wiring: enable MBEDTLS_SPAKE2P_C / the built-in algs in
crypto_adjust_config_enable_builtins.h and the SPAKE2+ prerequisite
checks in check_crypto_config.h.
- Tests: low-level write_key_share known-answer tests and setup
negatives (test_suite_spake2p), plus PSA-level Prover key-share KAT
(RFC 9383 vector via the ephemeral-injection hook), key-share
format/buffer/role/policy tests (test_suite_psa_crypto_pake).
Resolves part of #9347 (Prover key share).
Signed-off-by: Sigvart Hovland <sigvart.m@gmail.com>
Add component_tf_psa_crypto_test_spake2p_hooks: build the default config with MBEDTLS_TEST_HOOKS (ASan) and run the SPAKE2+ suites (ctest -R 'spake2p|psa_crypto_pake'). The hook exposes the ephemeral-injection seam used by the deterministic key-share known-answer tests (RFC 9383 Appendix C), which are otherwise skipped -- so without this component CI has no coverage of the SPAKE2+ known-answer vectors. Signed-off-by: Sigvart Hovland <sigvart.m@gmail.com>
Carve the Verifier-side slice of SPAKE2+ (RFC 9383) test coverage on top of
the Prover slice. The production code (built-in primitive, PSA built-in driver
and PSA core) already drives the server role symmetrically with the client:
mbedtls_spake2p_setup() accepts the public-key registration record w0||L,
spake2p_make_own_share() selects N (not M) for the server, and the PSA layer
maps PSA_PAKE_ROLE_SERVER / SPAKE2P_PUBLIC_KEY through setup and
psa_pake_output(KEY_SHARE). This change adds the missing Verifier tests:
- Low-level (test_suite_spake2p): Verifier key-share known-answer tests
(shareV = y*P + w0*N) against RFC 9383 Appendix C for P-256, P-384 and
P-521, via the MBEDTLS_TEST_HOOKS ephemeral-injection seam, plus a server
setup negative (w0||L of the wrong length).
- PSA-level (test_suite_psa_crypto_pake): Verifier setup role-mismatch
negative (public key rejected for the client role), key-share format
(P-256/P-384) and a Verifier key-share KAT reproducing the RFC 9383 P-256
HMAC server share byte-for-byte through psa_pake_output().
Per #9347, out-of-sequence server output is tolerated (no strict ordering
enforced yet) for testing convenience.
Resolves part of #9347 (Verifier key share).
Signed-off-by: Sigvart Hovland <sigvart.m@gmail.com>
Add the SPAKE2+ (RFC 9383) verifier transcript hash and the peer
key-share input path:
- mbedtls_spake2p_read_key_share() parses and validates the peer's
SEC1 public share (group-membership check, RFC 9383 Section 6).
- The transcript-encoding machinery (spake2p_tt_update,
spake2p_tt_update_point) absorbs the share-derivable prefix of the
transcript TT (RFC 9383 Section 3.3: Context, idProver, idVerifier,
M, N, shareP, shareV) once both shares are known. The remaining
secret-derived fields (Z, V, w0) belong to the key schedule and are
out of scope here.
- The built-in PSA PAKE driver and the PSA core state machine accept
psa_pake_input(PSA_PAKE_STEP_KEY_SHARE) for SPAKE2+, with the
existing prologue/epilogue rejecting out-of-order calls.
Testability: TT is internal and is normally only observable through the
downstream confirmation MAC / shared key, which are out of scope. A
MBEDTLS_TEST_HOOKS-gated accessor
(mbedtls_spake2p_test_get_transcript_prefix_hash) exposes Hash(prefix)
so the field encoding can be validated against the RFC 9383 Appendix C
P-256 vector independently of key derivation. New tests cover the
verifier transcript-prefix KAT and read_key_share input validation.
Signed-off-by: Sigvart Hovland <sigvart.m@gmail.com>
Add the SPAKE2+ (RFC 9383) key schedule and the verifier confirmation
MAC output, building on the verifier transcript hash slice (#9349).
Primitive (spake2p.c/.h):
- spake2p_hkdf(): HKDF (RFC 5869) with a nil salt, used by the key
schedule.
- spake2p_derive_keys(): once both shares are known, compute the
shared values Z and V, finalize the full RFC 9383 Section 3.3
transcript TT (adding Z, V and w0 to the share-derivable prefix),
and expand K_main into K_confirmP / K_confirmV / K_shared
(RFC 9383 Section 3.3-3.4). HMAC profile only; the CMAC/Matter MAC
arms are gated out for a later change.
- spake2p_mac() (HMAC arm only) and mbedtls_spake2p_write_confirm():
the verifier emits confirmV = MAC(K_confirmV, shareP).
- mbedtls_spake2p_read_key_share() now takes an RNG, needed for the
constant-time scalar multiplications behind Z and V, and triggers
the key schedule.
Driver (psa_crypto_pake.c): wire psa_pake_output(PSA_PAKE_STEP_CONFIRM)
to mbedtls_spake2p_write_confirm() and pass the PSA RNG through to
read_key_share. The Prover input(CONFIRM) check and get_shared_key
remain NOT_SUPPORTED (later issues). The PSA core SPAKE2+ state machine
(CONFIRM round) is already in place from the #9349 base.
Tests: low-level verifier confirmV KATs against RFC 9383 Appendix C for
P-256/P-384/P-521 (HMAC), a PSA-level confirmV KAT through
psa_pake_output(), and negative cases (confirm before the shares are
exchanged -> BAD_STATE at the primitive and PSA levels).
Signed-off-by: Sigvart Hovland <sigvart.m@gmail.com>
Add the prover (CLIENT) transcript-hash coverage and ordering on top of
the verifier transcript machinery from #9349/#9352.
The transcript accumulation (spake2p_update_tt_prefix over Context,
idProver, idVerifier, M, N, shareP, shareV) and the input(KEY_SHARE)
ordering in the PSA SPAKE2+ state machine (psa_spake2p_prologue/epilogue)
are role-agnostic and already correct for the CLIENT; this slice makes
that correctness explicit and tested:
* Add an invasive Prover transcript-prefix KAT (test_suite_spake2p)
that drives the CLIENT through set_context, output(shareP, injected
ephemeral x) and input(peer shareV), then asserts the accumulated TT
equals the RFC 9383 Appendix C P-256 value -- the same TT the verifier
KAT validates, confirming both parties compute an identical transcript.
* Add Prover out-of-order negatives (test_suite_psa_crypto_pake):
input(CONFIRM) before the key shares are exchanged, and a second
input(KEY_SHARE) within one exchange, both rejected with
PSA_ERROR_BAD_STATE.
Prover input(CONFIRM) (#9355), the confirmation exchange (#9367) and
get_shared_key (#9370) remain out of scope.
Signed-off-by: Sigvart Hovland <sigvart.m@gmail.com>
Add the SPAKE2+ (RFC 9383) prover (CLIENT) confirmation check. Once both key shares have been exchanged and the key schedule is derived, the prover verifies the verifier's confirmation MAC confirmV = MAC(K_confirmV, shareP) in constant time and fails if it does not match. - spake2p.c/.h: add mbedtls_spake2p_read_confirm(), which recomputes the expected peer MAC over the local share and compares it with mbedtls_ct_memcmp(), returning MBEDTLS_ERR_ECP_VERIFY_FAILED on mismatch or wrong length. - psa_crypto_pake.c: dispatch PSA_SPAKE2P_STEP_CONFIRM in the built-in PAKE input arm to mbedtls_spake2p_read_confirm(). The core state machine and step mapping for input(CONFIRM) already exist from #9352, so psa_crypto.c is unchanged. The prover OUTPUT(CONFIRM), verifier INPUT(CONFIRM) and get_shared_key paths remain out of scope. HMAC confirmation profile over P-256/384/521. Tests: low-level read_confirm KAT (accept the RFC 9383 Appendix C confirmV, reject a tampered MAC and a wrong length) for all three curves, and the PSA-level prover input(CONFIRM) accept + tamper-reject KAT. Signed-off-by: Sigvart Hovland <sigvart.m@gmail.com>
Add the prover (client) side of the SPAKE2+ (RFC 9383) confirmation exchange: once both key shares have been exchanged and the key schedule is derived, psa_pake_output(PSA_PAKE_STEP_CONFIRM) on the prover returns confirmP = MAC(K_confirmP, shareV). The production code path was already in place: the low-level primitive mbedtls_spake2p_write_confirm is role-parametrized (emits confirmP for CLIENT, confirmV for SERVER), the built-in driver's CONFIRM output arm is role-agnostic, and the core PSA state machine advances per input/output pair. This change is therefore test coverage and a ChangeLog entry only. Tests: - test_suite_spake2p: spake2p_prover_confirm_kat low-level known-answer tests for P-256/P-384/P-521 (P-256 is the RFC 9383 Appendix C confirmP vector; P-384/P-521 are derived with the same machinery that already reproduces the merged verifier confirmV rows), with too-early write_confirm rejected. - test_suite_psa_crypto_pake: spake2p_prover_confirm_psa_kat drives the full client+server handshake and asserts the client's confirmP via psa_pake_output(CONFIRM) under PSA_ALG_SPAKE2P_HMAC; plus spake2p_prover_confirm_output_too_early (output(CONFIRM) before shares -> PSA_ERROR_BAD_STATE). The verifier-side check of confirmP (#9367 part 2) and shared-key export (#9370) remain out of scope. Signed-off-by: Sigvart Hovland <sigvart.m@gmail.com>
Add the SPAKE2+ (RFC 9383) verifier (SERVER) side check of the prover's confirmation MAC. Once both key shares have been exchanged and the key schedule is derived, psa_pake_input(PSA_PAKE_STEP_CONFIRM) on the verifier verifies confirmP = MAC(K_confirmP, shareV) and fails on mismatch. The production path was already in place: the low-level primitive mbedtls_spake2p_read_confirm is role-agnostic (a server recomputes the prover's expected MAC over its own shareV with K_confirmP), and the built-in driver's input(CONFIRM) arm was wired in #9355. This change is therefore test coverage and a ChangeLog entry only. Tests (test_suite_psa_crypto_pake, HMAC-SHA-256 P-256): - spake2p_verifier_confirm_input_psa_kat drives the server through setup, its own key-share output (injected ephemeral y) and the prover share input, then asserts that psa_pake_input(CONFIRM) accepts the RFC 9383 Appendix C confirmP (926cc713...) -> PSA_SUCCESS. - spake2p_verifier_confirm_input_tampered flips one byte of confirmP and asserts psa_pake_input(CONFIRM) -> PSA_ERROR_INVALID_SIGNATURE. Shared-key export (#9370) remains out of scope. Signed-off-by: Sigvart Hovland <sigvart.m@gmail.com>
Expose the RFC 9383 K_shared shared secret once the SPAKE2+ handshake has
completed in both directions. The key schedule already derives K_shared in
read_key_share; this change adds the export path:
- mbedtls_spake2p_get_shared_key() copies out K_shared.
- A dedicated `confirmed` context flag, set only when the peer's
confirmation MAC has been successfully verified in read_confirm, gates
the export. This is a defence-in-depth security gate, independent of the
PSA stage / call-sequence enforcement: the shared key is never released
before key confirmation completes.
- Wire the built-in PSA PAKE driver (get_implicit_key) and the PSA core
(psa_pake_get_shared_key) SPAKE2+ arms, gated on PSA_SPAKE2P_FINISHED.
Add a full client+server handshake KAT for P-256/P-384/P-521 that exports
K_shared on both sides and checks it equals the RFC 9383 Appendix C value,
plus a security negative that calls get_shared_key before confirmation and
asserts it fails via the `confirmed`-flag path.
Signed-off-by: Sigvart Hovland <sigvart.m@gmail.com>
Add end-to-end SPAKE2+ tests driven through the public PSA PAKE API with real
(randomly generated) ephemerals -- no MBEDTLS_TEST_HOOKS injection -- so they
run in a default build, on par with `ecjpake_rounds`:
- spake2p_rounds: two independent psa_pake_operation_t (client + server) run
the full handshake (setup, key-share exchange, mutual confirmation), then
each exports the shared secret via psa_pake_get_shared_key(); the two shared
keys must be identical. Covers the HMAC ciphersuites P-256/P-384/P-521.
- spake2p_rounds_wrong_password: the defining PAKE security property -- when
the client's key pair (w0||w1) does not match the server's registration
record (w0||L), the key-share exchange still completes but key confirmation
MUST fail (PSA_ERROR_INVALID_SIGNATURE) on both sides; no shared key is
established.
These are the first tests to exercise psa_pake_get_shared_key() for SPAKE2+ and
the first SPAKE2+ end-to-end tests that run without test hooks.
Signed-off-by: Sigvart Hovland <sigvart.m@gmail.com>
spake2p_rounds and spake2p_rounds_wrong_password each repeated the operation setup (cipher suite, key import, setup/role/user/peer) and the KEY_SHARE exchange inline. Reuse the existing spake2p_setup_client / spake2p_setup_server helpers and add a small spake2p_exchange_key_shares helper, removing ~80 lines of duplication with no change in coverage. Signed-off-by: Sigvart Hovland <sigvart.m@gmail.com>
Wire the CMAC arm into the SPAKE2+ confirmation MAC helper so the PSA_ALG_SPAKE2P_CMAC ciphersuites work for all supported curves. The confirmation MAC becomes AES-CMAC-128 instead of HMAC-Hash (RFC 9383 Section 3.4); the key schedule stays HKDF and the rest of the handshake is unchanged. Add RFC 9383 Appendix C CMAC known-answer tests (verifier confirm, prover confirm, prover read-confirm with tamper-reject, and the full handshake shared key) for the P256-SHA256-HKDF-SHA256-CMAC-AES-128-SHA256 ciphersuite, validated byte-for-byte against the RFC. Signed-off-by: Sigvart Hovland <sigvart.m@gmail.com>
Extend the e2e rounds tests (spake2p_rounds, spake2p_rounds_wrong_password) to the CMAC ciphersuite: full two-party handshake with random ephemerals through psa_pake_get_shared_key() (P-256/P-384/P-521) and the mismatched-password confirmation-failure case (P-256), all in a default non-hooks build. Signed-off-by: Sigvart Hovland <sigvart.m@gmail.com>
Make PSA_ALG_SPAKE2P_MATTER use the draft-bar-cfrg-spake2plus-02 key
schedule rather than RFC 9383. The Matter ciphersuite is the single
P256-SHA256-HKDF-HMAC-SHA256 profile: same curve, hash and HMAC-SHA-256
MAC as the RFC 9383 HMAC profile, but the transcript digest is consumed
differently.
draft-02 (Matter) vs RFC 9383 key schedule:
- Kae = Hash(TT) is split into Ka || Ke (two equal halves).
- Kca || Kcb = HKDF(nil, Ka, "ConfirmationKeys"), each hash_len/2.
Kca -> K_confirmP, Kcb -> K_confirmV.
- The shared secret is Ke (the second half of Kae), not an
HKDF(K_main, "SharedKey") expansion.
- The confirmation MAC stays the full HMAC-SHA-256 tag.
Implementation:
- New mbedtls_spake2p_kdf_type {RFC9383, MATTER} and a kdf_type context
field; mbedtls_spake2p_setup() gains a kdf parameter (Matter is only
valid with the HMAC profile) and sets the halved conf/shared key
lengths for Matter.
- spake2p_derive_keys() gets the Matter branch; the RFC 9383 path is
unchanged.
- The PSA built-in PAKE driver maps PSA_ALG_SPAKE2P_MATTER to the
Matter KDF.
Tests (validated against the deterministic connectedhomeip interop
vector in tests/data_files/spake2p_matter_p256_interop.txt):
- New spake2p_matter_kat (test_suite_spake2p): full builtin handshake,
checks shareP, shareV, confirmP, confirmV and K_shared = Ke
byte-for-byte.
- Updated the PSA-layer Verifier-confirm Matter KAT to the draft-02
confirmV vector (the share KATs are KDF-independent and unchanged).
Registration (#9381) is intentionally not included.
Signed-off-by: Sigvart Hovland <sigvart.m@gmail.com>
Extend the e2e rounds tests (spake2p_rounds, spake2p_rounds_wrong_password) to the Matter ciphersuite (PSA_ALG_SPAKE2P_MATTER, P-256, draft-02 key schedule): full two-party handshake with random ephemerals through psa_pake_get_shared_key(), and the mismatched-password confirmation-failure case, in a default non-hooks build. Signed-off-by: Sigvart Hovland <sigvart.m@gmail.com>
Extend psa_key_derivation_output_key() to handle PSA_KEY_TYPE_SPAKE2P_KEY_PAIR. SPAKE2+ registration (RFC 9383 Section 3.2) derives the prover key pair (w0, w1) from a key-derivation operation rather than importing it: for each scalar, ceil(bits/8)+8 bytes are drawn from the operation and reduced modulo the group order n (the extra 64 bits keep the modular bias negligible), yielding the importable/usable key pair w0 || w1. Only the short-Weierstrass secp_r1 ciphersuites are supported, used via PSA_ALG_SPAKE2P_HMAC. Gated on a new config option PSA_WANT_KEY_TYPE_SPAKE2P_KEY_PAIR_DERIVE. Tests: a registration KAT (HKDF-SHA-256 -> w0 || w1, cross-checked against an independent reference computation), a determinism check, and negative tests rejecting unsupported curves. Signed-off-by: Sigvart Hovland <sigvart.m@gmail.com>
Description
Implement a spake2+ driver and expose SPAKE2+ through the PAKE interface of PSA. Goal of this PR is to address all the issues in SPAKE2+ Issues MbedTLS tracker.
This is based on the work of @KiruthikPrakash and @kusumitg:
@KiruthikPrakash
@kusumitg
Note: This is an AI-assisted(guided) implementation, I've reviewed it but parts of the code is generated using Opus-4.8 and reviewed using Fable.
PR checklist
Please remove the segment/s on either side of the | symbol as appropriate, and add any relevant link/s to the end of the line.
If the provided content is part of the present PR remove the # symbol.
Notes for the submitter
Please refer to the contributing guidelines, especially the
checklist for PR contributors.
Help make review efficient: