Skip to main content

cratestack_cose/
lib.rs

1//! `cratestack-cose`: the COSE envelope of ADR 0006, unary part (P0).
2//!
3//! A signed body is the codec's output wrapped as a COSE_Sign1 (tag 18) or
4//! COSE_Mac0 (tag 17), bound to the request through external AAD that is
5//! rebuilt on both sides and never sent:
6//!
7//! ```text
8//! typed value ──CborCodec──▶ payload bytes ──CoseEnvelope──▶ COSE_Sign1 / COSE_Mac0
9//!                                 ▲                               │
10//!                                 └──────── verified as-is ◀──────┘
11//! ```
12//!
13//! - [`CoseEnvelope`] implements `cratestack_core::CratestackEnvelope`, and
14//!   also offers typed methods ([`CoseEnvelope::open_request`] and the
15//!   rest) that need no `CratestackContext`, for the axum layer
16//!   (cratestack#1006), which runs before a context exists.
17//! - [`CoseSigner`] and [`CoseVerifierResolver`] are the key seams: a KMS or
18//!   HSM signs without exporting the key, and a resolver may return several
19//!   candidates for one `kid`.
20//! - [`external_aad`] defines the binding (§4), which includes the
21//!   receiving service's `audience` (never empty, and distinct from the
22//!   audience the service seals its own outbound requests for);
23//!   [`request_digest`] and [`request_digest_unsigned`] bind a response to
24//!   its request, the latter through the client's [`RequestNonce`]
25//!   (`Cratestack-Nonce`), and each returns its digest together with the
26//!   `RequestKind` the response AAD binds; [`thumbprint`] the RFC 9679 key
27//!   ids (§3).
28//! - [`KeyProviderMacKeys`] turns `cratestack_core::KeyProvider` secrets
29//!   into Mac0 keys.
30//!
31//! **Wire format:** the server layer (cratestack#1006) and the Rust client
32//! (cratestack#1007) ship together in the release that freezes binding
33//! version 1 (#1082); from it on the wire format changes only with a new
34//! [`BINDING_VERSION`].
35//!
36//! **Algorithms:** Ed25519 (`-19`, the default) and ESP256 (`-9`) for
37//! Sign1, HMAC 256/64 (`4`) and 256/256 (`5`) for Mac0. Nothing else is
38//! accepted, including the deprecated `-8` and `-7` (see [`CoseAlg`]).
39//!
40//! **Errors (§10):** every failed check is the same
41//! `CratestackError::Unauthorized` carrying [`UNAUTHENTICATED`]; a failing
42//! key resolver, nonce store or signer is `CratestackError::Internal`, and
43//! so is local misuse (see [`CoseEnvelope`]).
44//!
45//! **The `auth` feature** (off by default) adds `cratestack_cose::auth`: the
46//! `cratestack-auth` adapters (`ServiceSigningKey` as a signer,
47//! `DeviceKeyResolver` as a resolver), the bridge to its Redis nonce
48//! store, and the COSE enrolment challenge code moved out of
49//! `cratestack-auth`. It is this crate's only edge to `cratestack-auth`.
50//!
51//! **Claim objects** ([`claims`]): [`sign_claims`] / [`open_claims`] produce and
52//! verify a self-contained `COSE_Sign1` with a mandatory `typ`, RFC 9597 CWT
53//! claims and no freshness or replay check, for a payload signed offline and
54//! delivered later. Unrelated to the envelope: neither opens as the other.
55//!
56//! **Not here yet:** `chain` streams (P1) and `window` replay (P2).
57//!
58//! # Where this departs from the ADR's sketch
59//!
60//! - The outer structure is emitted and parsed by hand, not with `coset`
61//!   (see the private `cbor` module's doc); `coset` is a dev-dependency
62//!   that the tests check the bytes against.
63//! - Algorithms are the closed [`CoseAlg`], not `coset::iana::Algorithm`.
64//! - The payload is encoded in place only on the `seal_value` path
65//!   (`CratestackEnvelope::seal_value`, [`CoseEnvelope::seal_request_value`]
66//!   and [`CoseEnvelope::seal_response_value`]). `seal` receives bytes that
67//!   are already encoded and copies them into the message once.
68//! - For in-process signers (HMAC, ESP256 and Ed25519) the signature is
69//!   computed over the to-be-signed structure in pieces, with no copy of
70//!   the payload; Ed25519 runs both PureEdDSA passes over the pieces (see
71//!   [`Ed25519Signer`]). A KMS or HSM signer keeps the default
72//!   [`CoseSigner::sign_chunks`] and is handed the structure in one buffer.
73//!   Verification never builds it contiguously.
74//! - ESP256 signatures are low-`s` only: the sealer normalises them, the
75//!   opener rejects a high `s`, so no third party can re-spell a signed
76//!   message.
77
78mod aad;
79mod alg;
80#[cfg(feature = "auth")]
81pub mod auth;
82mod call;
83mod cbor;
84pub mod claims;
85mod envelope;
86mod error;
87mod header;
88mod keys;
89mod maybe_send;
90mod open;
91mod opened;
92mod replay;
93mod seal;
94mod tbs;
95pub mod thumbprint;
96mod wire;
97
98pub use aad::{BINDING_VERSION, external_aad};
99pub use alg::{CoseAlg, CoseMode};
100pub use call::{CallBinding, contract_header_value};
101pub use claims::{
102    ClaimsError, CwtClaims, MAX_AUD_LEN, MAX_CTI_LEN, MAX_TYP_LEN, OpenedClaims, open_claims,
103    sign_claims,
104};
105/// Defined in `cratestack-core` since the cratestack#1006 API review (so
106/// the axum layer's `envelope` feature needs no COSE crate), and
107/// re-exported here unchanged: every path under `cratestack_cose` keeps
108/// working.
109pub use cratestack_core::{
110    NONCE_HEADER, NONCE_HEADER_VALUE_LEN, REQUEST_NONCE_LEN, RequestNonce, request_digest,
111    request_digest_unsigned,
112};
113pub use envelope::{CoseEnvelope, CoseEnvelopeBuilder, CoseRole};
114pub use error::UNAUTHENTICATED;
115pub use keys::{
116    CoseSigner, CoseVerifierResolver, CoseVerifyKey, Ed25519Signer, ExternalSigner, HmacSecret,
117    HmacSigner, KeyProviderMacKeys, MIN_HMAC_SECRET_LEN, P256Signer, RegistryVerifierResolver,
118    StaticVerifierResolver,
119};
120pub use maybe_send::{MaybeSend, MaybeSendSync};
121pub use opened::Opened;
122pub use replay::{DEFAULT_SKEW_SECS, RANDOM_CTI_LEN, random_request_nonce};