cose2
CBOR Object Signing and Encryption (COSE, RFC 9052) and CBOR Web Token
(CWT, RFC 8392) for Rust, built on cbor2 and its
#[derive(cbor2::Cbor)] macro.
cose2 models the COSE wire structures and CWT claims and leaves the
cryptography to you: signing, verification, MAC and content encryption are
supplied through the Signer, Verifier, Macer and Encryptor
traits, so the default build carries no cryptographic dependencies.
Pick any crypto library (e.g. ed25519-dalek, p256, aes-gcm, hmac) and
implement the relevant trait.
Enable the optional crypto-ring feature, or the aggregate crypto feature,
to use the built-in crypto module backed by [ring]. The ring backend
implements Ed25519, ES256, ES384, RS256/384/512, PS256/384/512, HMAC
256/64, HMAC 256/256, HMAC 384/384, HMAC 512/512, A128GCM, A256GCM and
ChaCha20/Poly1305. Algorithms outside ring's support are rejected at provider
construction.
- Typescript version: https://github.com/ldclabs/cose-ts
- Golang version: https://github.com/ldclabs/cose
Features
- Messages —
COSE_Sign1,COSE_Sign,COSE_Mac0,COSE_Mac,COSE_Encrypt0,COSE_Encrypt,COSE_recipient. - Keys —
COSE_Keyobjects (Key) and non-empty key sets (KeySet) with typed accessors forkty,kid,alg,key_opsandBase IV. - Headers — protected/unprotected
Headermaps with integer or text labels and the full IANA parameter registry underiana. - CWT — typed
cwt::Claims, a label-keyedcwt::ClaimsMap, and acwt::Validatorfor expiry, not-before, issued-at, issuer and audience. - KDF context —
KdfContext,PartyInfo,SuppPubInfo(RFC 9053 §5.2). - Tagging — tagged or untagged messages, with optional CWT and
self-described CBOR prefixes handled transparently. Newly encoded COSE
messages and CWT claims use their registered CBOR tags through
#[derive(cbor2::Cbor)]. - Optional crypto —
crypto-ringprovidesRingSigner,RingVerifier,RingMacerandRingEncryptorimplementations behind a feature flag.
Quick start
use ;
// Plug in your own crypto. (This toy "signer" is for illustration only.)
;
;
let mut msg = new;
let encoded = msg.sign_and_encode?;
let verified = verify_and_decode?;
assert_eq!;
# Ok::
Use the API by task
| Task | Start with | Notes |
|---|---|---|
| Sign one embedded payload | Sign1Message::sign_and_encode / Sign1Message::verify_and_decode |
The common COSE_Sign1 flow. |
| Sign one detached payload | Sign1Message::sign_detached_and_encode / Sign1Message::verify_detached_and_decode |
The wire payload is nil; carry the payload out of band. |
| Sign with multiple signers | SignMessage |
Each signer has its own Signature. |
| MAC one payload | Mac0Message::compute_and_encode / Mac0Message::verify_and_decode |
Symmetric authentication without recipients. |
| MAC with recipients | MacMessage |
Recipient-layer cryptography stays application-owned. |
| Encrypt one payload | Encrypt0Message::encrypt_and_encode / Encrypt0Message::decrypt_and_decode |
Requires a full IV, or Partial IV plus Encryptor::base_iv. |
| Encrypt with recipients | EncryptMessage |
cose2 validates recipient structure but does not wrap or agree the CEK. |
| Async/remote signing | prepare_signature / prepare_signatures, then set_signature / set_signatures |
Sign the returned Sig_structure bytes outside the synchronous trait. |
| Async/remote MAC | prepare_tag / prepare_detached_tag, then set_tag |
MAC the returned MAC_structure bytes outside the synchronous trait. |
| Async/remote encryption | prepare_encryption then set_ciphertext |
Encrypt with the returned nonce and Enc_structure AAD. |
| Work with CWT claims | cwt::Claims, cwt::ClaimsMap, cwt::Validator |
Use ClaimsMap when custom claims must be preserved. |
| Work with COSE keys | Key, KeySet |
KeySet::lookup(kid) returns all matches because kid is not unique. |
Runnable examples
These examples are intended as copy/paste starting points for humans and AI agents:
For a compact decision checklist — including a crypto-ring algorithm
recipe table mapping each algorithm to its required COSE key
parameters — see the Agent guide for cose2. Agents modifying this
crate's source should start from AGENTS.md.
Design notes
- Header, key and claim maps share one ordered type,
CoseMap, keyed byLabel(int/tstr). Values are [cbor2::Value]. Headeris aCoseMapnewtype with typed accessors for common COSE parameters (alg,crit,kid,iv,Partial IV) while still dereferencing to the underlying map for custom labels. Message and recipient decoding rejects malformedcritvalues and protected/unprotected bucket label collisions.Keyrequireskty;KeySetencodes/decodes as a non-empty COSE_KeySet andlookupreturns all keys with a matchingkid, since COSE key identifiers are not unique.algvalues in crypto traits areOption<Label>, so both registered integer algorithms and private text-string algorithms are representable.- The default build has no crypto dependency. The
crypto-ringfeature offers ready-to-use providers for the algorithms listed above, while unsupported algorithms return an explicit error instead of falling back to a mismatched primitive. - The protected header is captured as raw bytes on decode and reused verbatim
in the
Sig_structure/MAC_structure/Enc_structure, so signatures made with non-canonical encodings still verify. - Top-level COSE message wire types use named Rust structs with
#[cbor(tag = ..., array)], preserving the COSE array wire shape while declaring their IANA CBOR tags. CWT claims declare their IANA CBOR tag with#[cbor(tag = 61)]. Decoders still accept untagged COSE messages and untagged claim maps for compatibility. - Detached payloads are explicit: use
sign_detached*,compute_detached*,verify_detached*, orverify_detached_and_decode. - Detached ciphertext is explicit: use
encrypt_detached*anddecrypt_detached*for COSE_Encrypt/COSE_Encrypt0 messages whose ciphertext field is encoded asnil. Recipientvalidates RFC 9052 recipient-layer structure for registered direct, key-wrap, key-transport and key-agreement algorithms. Actual key wrapping/agreement cryptography remains delegated to application code.- Encryption requires an explicit plaintext payload; use
Some(Vec::new())for an empty plaintext. - Newly built protected headers and keys serialize with canonical (deterministic) CBOR (RFC 8949 §4.2.1).
- Nonces are taken from the unprotected
IV, or derived fromPartial IVby XORing the left-padded partial value with [Encryptor::base_iv]. This crate generates no randomness.
Testing
cargo test runs the unit, integration and doc tests, including a byte-exact
reproduction of the RFC 9052 Appendix C.4.1 COSE_Encrypt0 vector.
Coverage measured with cargo llvm-cov is 100% of lines and functions;
the remaining uncovered regions are unreachable error-propagation arms on
serialization that cannot fail.
License
Dual-licensed under MIT or the UNLICENSE.