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], or crypto-aws-lc-rs
to back it with aws-lc-rs instead. Both backends expose the same providers
and implement Ed25519/EdDSA, ESP256/ES256, ESP384/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 the backend's support are rejected at
provider construction. When both backend features are enabled, crypto-ring
takes precedence.
Alternatively, enable crypto-ed25519-dalek for a standalone Ed25519
Signer/Verifier (module ed25519) backed by ed25519-dalek, or
crypto-aes-gcm for an AES-GCM (A128GCM/A256GCM) Encryptor (module
aes_gcm) backed by aes-gcm — both without a ring/aws-lc-rs
dependency.
- 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 registry constants underiana, including legacy RFC 8152 full countersignature parsing and verification. - CWT — typed
cwt::Claims, a label-keyedcwt::ClaimsMap, and acwt::Validatorfor expiry, not-before, issued-at, issuer and audience. - SD-CWT — the workspace also includes
sd-cwt, a companion crate for selective-disclosure claims,simple(59), tag-60 redacted elements,sd_claims, and Holder/Verifier restoration. - KDF context —
KdfContext,PartyInfo,SuppPubInfo(RFC 9053 §5.2). - Tagging — tagged or untagged COSE messages, with optional CWT and
self-described CBOR wrappers parsed semantically.
Claims::to_vecemits the untagged claims map used as a COSE payload;message.to_cwt_vec()emits the RFC 839261(COSE_Tagged(...))envelope. - Optional crypto —
crypto-ringorcrypto-aws-lc-rsprovidesRingSigner,RingVerifier,RingMacerandRingEncryptorimplementations behind a feature flag, backed byringoraws-lc-rsrespectively.BackendSigner,BackendVerifier,BackendMacerandBackendEncryptorprovide backend-neutral aliases for new code.crypto-ed25519-dalekadds anEd25519Signer/Ed25519Verifierbacked byed25519-dalek, andcrypto-aes-gcmadds anAesGcmEncryptorbacked byaes-gcm.
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, then message.to_cwt_vec() |
Claims::to_vec is the untagged payload map; Claims::extra preserves custom keys. |
| Work with SD-CWT claims | sd-cwt companion crate |
Issue tag-58/62 pre-issuance claims, write sd_claims, and restore Holder/Verifier presentations. |
| 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.
Applications upgrading from 0.4 should read Migrating from cose2 0.4 to 0.5, especially the corrected CWT wrapper and typed claim changes.
For selective-disclosure credentials, start with the
sd-cwt README and its runnable basic example. The
example shows pre-issuance redaction requests, issuer signing with
Sign1Message, Holder validation, and Verifier presentation with a disclosure
subset.
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. Verification and decryption reject unknown critical headers unless the provider declares that it processes them.Keyrequireskty; built-in providers enforcekey_ops.KeySetprocesses each entry independently as required by RFC 9052;from_slice_strictis available when one malformed entry should reject the entire set.lookupreturns all matchingkidvalues.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-ringandcrypto-aws-lc-rsfeatures offer 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. Mutating the public protected header after those bytes are prepared invalidates the message state. - Decoders enforce protocol wire types, reject duplicate map keys recursively,
accept semantically equivalent CBOR tag encodings, and require a CWT tag to
wrap a tagged COSE message. The old
61(claims-map)form can be read only throughClaims::from_slice_legacy_tagged. - 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.
CI runs formatting, clippy, tests, rustdoc, MSRV, backend-specific builds,
coverage and fuzz-target compilation. The coverage job enforces the threshold
recorded in .github/workflows/ci.yml; it does not claim unreachable paths are
covered.
Lightweight performance probes are available with:
License
Licensed under the MIT License.