encoding.cose: add COSE (RFC 9052) and CWT (RFC 8392) modules #18
This PR adds two complementary modules to the standard library: - **`encoding.cose`** — CBOR Object Signing and Encryption, signing + MAC subset of [RFC 9052][rfc9052] and [RFC 9053][rfc9053]. - **`encoding.cwt`** — CBOR Web Tokens ([RFC 8392][rfc8392]) built on top of `cose`. Both modules sit alongside `encoding.cbor` (merged in #27018) and use its codec under the hood. No new C code, no new third-party crypto: everything is built on the primitives already in `vlib/crypto` (`ecdsa`, `ed25519`, `hmac`, `sha256`, `sha512`). [rfc9052]: https://www.rfc-editor.org/rfc/rfc9052 [rfc9053]: https://www.rfc-editor.org/rfc/rfc9053 [rfc8392]: https://www.rfc-editor.org/rfc/rfc8392 ## Why CBOR is everywhere: WebAuthn / FIDO2 attestations, IETF SUIT (firmware updates over LWM2M), Matter (smart home), CoAP / OSCORE, EDHOC, the European Digital Identity Wallet… All of them serialise their tokens as **COSE** or **CWT**. ## What's covered | Message type | Tag | Status | | ---------------- | --- | ------ | | `COSE_Sign1` | 18 | ✅ | | `COSE_Sign` | 98 | ✅ | | `COSE_Mac0` | 17 | ✅ | | `COSE_Mac` | 97 | ✅ (direct mode) | | Algorithm | IANA | Status | | ------------- | ---- | ------ | | `ES256` | -7 | ✅ | | `ES384` | -35 | ✅ | | `ES512` | -36 | ✅ (P-521 + SHA-512) | | `EdDSA` | -8 | ✅ Ed25519 | | `HMAC 256/64` | 4 | ✅ | | `HMAC 256/256`| 5 | ✅ | | `HMAC 384/384`| 6 | ✅ | | `HMAC 512/512`| 7 | ✅ | CWT models the seven RFC 8392 §3 standard claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `cti`) plus pass-through for application claims, and handles the optional outer CBOR tag 61 wrapper. ## Module surface ```v ignore import encoding.cose import encoding.cwt // Sign and verify a payload (Sign1 / EdDSA). signed := cose.sign1('payload'.bytes(), priv_key, protected: hp)! got := cose.verify1(signed, pub_key)! // MAC a payload (Mac0 / HMAC-SHA256). maced := cose.mac0('payload'.bytes(), sym_key, protected: hp)! got2 := cose.verify_mac0(maced, sym_key)! // Sign and verify a CBOR Web Token. token := cwt.sign(claims, priv_key, protected: hp)! back := cwt.verify(token, pub_key)! ``` A complete example program lives at `examples/cose_cwt.v`. ## Conformance / test vectors The `cose-wg/Examples` repository is the canonical interop corpus. Its relevant fixtures are vendored under `vlib/encoding/cose/tests/cose_wg/` and `vlib/encoding/cwt/tests/rfc8392/`, and run as part of `v test`. For deterministic algorithms (EdDSA, all HMAC variants) the V *output* is validated byte-for-byte; for ECDSA (randomised signatures) each reference message is verified. The negative `*-fail-*` vectors exercise the verifier's rejection paths against externally produced bad messages. | Vector | Type | Algorithm | Mode | | --- | --- | --- | --- | | `ecdsa-sig-01` | Sign1 | ES256 | verify | | `ecdsa-sig-02` | Sign1 | ES384 | verify + roundtrip | | `ecdsa-sig-03` | Sign1 | ES512 / P-521 | verify + roundtrip | | `eddsa-sig-01` | Sign1 | EdDSA | **bytes-exact** | | `eddsa-01` | Sign | EdDSA | verify (multi-signer wrapper) | | `sign1-fail-01..04` | Sign1 | ES256 | reject (bad tag, tampered payload, replaced alg) | | `sign1-pass-02` | Sign1 | ES256 | verify with external AAD | | `HMac-01` | Mac | HS256 | verify (single direct recipient) | | `HMac-04` | Mac | HS256 | reject (corrupted tag) | | `HMac-05` | Mac | HS256/64 | verify | | `HMac-enc-01` | Mac0 | HS256 | **bytes-exact** | | `HMac-enc-02` | Mac0 | HS384 | **bytes-exact** | | `HMac-enc-03` | Mac0 | HS512 | **bytes-exact** | | `HMac-enc-05` | Mac0 | HS256/64 | **bytes-exact** | | RFC 8392 A.3 | CWT (Sign1) | ES256 | verify | | RFC 8392 A.4 | CWT (Mac0) | HS256/64 | **bytes-exact** | In addition to the public corpus: * `ec_signature_test.v` — DER ↔ R‖S conversion edge cases (leading zeros, MSB-set sign disambiguation, P-521 long-form lengths). * `structures_test.v` — `Sig_structure` / `MAC_structure` byte output matches RFC 9052 §4.4 / §6.3. * `headers_test.v` — canonical key sorting (RFC 8949 §4.2.1), empty-protected-as-zero-length-bstr rule, unknown-label preservation. * `sign1_test.v` / `sign_test.v` / `mac0_test.v` / `mac_test.v` — sign/verify roundtrip per algorithm; tampered-payload and wrong-key rejection paths; detached-payload and external-AAD modes; sanity caps on signer/recipient array counts; unknown `crit` label rejection per RFC 9052 §3.1. * `cwt_test.v` — Claims Set encoding, the `aud` tstr-vs-array form rule from RFC 7519/8392, optional tag-61 wrapper, RFC 8392 A.3 and A.4 reproduction, `nbf`/`exp` validity-window helper. I've also tested these modules against lib in other languages to check interop. ## Out of scope (deliberate) * **AEAD encryption** (`COSE_Encrypt0` / `COSE_Encrypt`) — needs AES-GCM, AES-CCM, ChaCha20-Poly1305 in `vlib/crypto`, which is a separate effort. Adding the message types later is purely additive. * **RSA signatures** — RFC 9053 lists `PS256/384/512` and `RS256/384/512` (legacy). Adding them is mechanical if `vlib/crypto` ships RSA-PSS, but the demand is small relative to ECDSA + EdDSA. * **Key agreement / wrap** modes (ECDH-ES, A128KW…) — these belong with the encryption work since they're meaningful only when there is something to encrypt. * **Compressed EC points** in COSE_Key. * **Counter signatures** (RFC 9338).