basil-cose
A strict, deterministic COSE (RFC 9052/9053) profile for basil's sealed invocations. Broker-free and publishable, it is shared by the basil broker, the basil client crate, and basil users.
Constructions
| Construction | Shape | Build | Verify / open |
|---|---|---|---|
| Signed | bare COSE_Sign1 (tag 18) |
build_signed |
verify_signed |
| Sealed | COSE_Sign1 over an embedded tagged COSE_Encrypt |
build_sealed |
verify_sealed, then VerifiedSealed::open |
| Seal-only | bare COSE_Encrypt (tag 96) |
build_encrypted |
decode_encrypted, then EncryptedMessage::open |
Algorithms: EdDSA (−8, Ed25519) or ES256 (−7, ECDSA P-256 + SHA-256) signatures; ECDH-ES + HKDF-256 (−25) with X25519 key agreement; A256GCM (3) or ChaCha20-Poly1305 (24) content encryption. The content algorithm is a parameter of every encrypting entry point. ES256 signing is deterministic (RFC 6979), so both signature algorithms re-sign byte-identically.
Profile guarantees
- Deterministic encoding by construction (in-tree codec over
minicbor: canonical map-key order, definite lengths, minimal integer heads), plus an RFC 8949 §4.2 re-encode-and-compare check on every decode, release builds included. - Strict decode: rejects untagged/wrong-tag structures, indefinite
lengths, non-minimal or otherwise non-deterministic encodings, duplicate or
unknown labels, text labels, wrong CBOR types, unknown algorithm
codepoints,
critviolations, claims in unprotected headers, and recipient arrays with length ≠ 1. Error enums are closed and diagnostic, with no forward-compatibleUnknownarms, and no secret bytes in any error. - Secure by construction: nonces and X25519 ephemerals are generated by
the library (
getrandom): there is no caller-supplied-nonce path. Every secret intermediate (private keys, ECDH shared secret, derived CEK, recovered plaintext) isZeroizing. Low-order (non-contributory) ephemeral keys are rejected before key derivation. Ed25519 verification usesverify_strict. - Caller-supplied
external_aadon bothSig_structureandEnc_structure, and KDF PartyU/PartyV identities carried in the recipient protected headers (−21/−24) with the RFC 9053 §5.2COSE_KDF_Contextas the HKDF info, so an opener that is not the sealer (for example a broker unseal-in-place RPC) can rebuild the KDF context from the message alone, and may pin expected identities.
Claims
Claims ride in the protected header as a CWT map (header 15) plus the basil
private labels (-70001..=-70005, module label): in-reply-to, request
hash (SHA3-256, helper request_hash), sender key id (must equal the outer
kid), response key id, and response subject. MessageRole
(request/response/peer) validators enforce claim shape;
ValidationParams parameterizes clock skew, TTL bounds, and the audience
allow-list (now is injected, never sampled: the crate is state-free and
owns no time policy).
Keys
Key material stays behind the AFIT traits Signer, Verifier, and
Recipient (async so broker-backed implementations can await an RPC; the
shipped local implementations complete synchronously): Ed25519Signer,
Ed25519Verifier, X25519Recipient (+ X25519RecipientPublic as the seal
target).
no_std
The crate is #![no_std] + alloc and obtains production randomness through
getrandom.
Features
fixtures: deterministic build variants (build_sealed_with_parts,build_encrypted_with_parts) that accept caller-supplied ephemeral/nonce parts, for test vectors only. Never enable on production paths.
Wire API
The COSE api is described in Sealed invocations and is accessible from both Rust and Go clients.