mid-verify's three signature checks (checks.rs: genesis self-signature, chain entries, the JWS) decode the 64-byte r || s with Signature::from_slice and call verify_prehash as given. ECDSA is malleable: for any valid (r, s), (r, n − s) verifies too, so a token can be re-encoded without the key. Anything keyed on the signature bytes (replay windows, dedup, audit trails) sees two identities for one token.
The device side (rusty_esp_mid-core::verify_prehash, Janus) rejects high-s: if sig.normalize_s().is_some() { return Err(Crypto) }. The producers (mid-issuer, kms-client, the device key) already emit low-s via normalize_s() on sign, so a reject in the verifier costs no legitimate token.
Evidence: Janus rusty_esp_mid-core/tests/oracle.rs::high_s_twin_refused_here_and_its_fate_upstream_is_pinned builds a self-issued token, flips s to n − s, and asserts that mid_verify::verify_mid_response accepts the twin (it does, 2026-09-02). The test fails the day this changes, on purpose.
Suggested fix (one line in decode_signature / signature_from_raw, plus a VerifyError::SignatureHighS so the reason is distinguishable):
let sig = Signature::from_slice(bytes).map_err(|_| VerifyError::SignatureInvalidScalars)?;
if sig.normalize_s().is_some() {
return Err(VerifyError::SignatureHighS);
}
Not folded into PR #1 (the no_std split) because it is a behaviour change and deserves its own review. kms-verifier has not been checked with a twin yet.
🤖 Generated with Claude Code
mid-verify's three signature checks (checks.rs: genesis self-signature, chain entries, the JWS) decode the 64-byter || swithSignature::from_sliceand callverify_prehashas given. ECDSA is malleable: for any valid(r, s),(r, n − s)verifies too, so a token can be re-encoded without the key. Anything keyed on the signature bytes (replay windows, dedup, audit trails) sees two identities for one token.The device side (
rusty_esp_mid-core::verify_prehash, Janus) rejects high-s:if sig.normalize_s().is_some() { return Err(Crypto) }. The producers (mid-issuer,kms-client, the device key) already emit low-s vianormalize_s()on sign, so a reject in the verifier costs no legitimate token.Evidence: Janus
rusty_esp_mid-core/tests/oracle.rs::high_s_twin_refused_here_and_its_fate_upstream_is_pinnedbuilds a self-issued token, flipsston − s, and asserts thatmid_verify::verify_mid_responseaccepts the twin (it does, 2026-09-02). The test fails the day this changes, on purpose.Suggested fix (one line in
decode_signature/signature_from_raw, plus aVerifyError::SignatureHighSso the reason is distinguishable):Not folded into PR #1 (the no_std split) because it is a behaviour change and deserves its own review.
kms-verifierhas not been checked with a twin yet.🤖 Generated with Claude Code