Skip to main content

cratestack_cose/envelope/
mod.rs

1//! [`CoseEnvelope`]: the COSE implementation of `CratestackEnvelope`.
2
3mod builder;
4mod open_api;
5mod trait_impl;
6
7use std::fmt;
8use std::sync::Arc;
9
10use bytes::Bytes;
11use cratestack_core::{Binding, CratestackCodec, CratestackError, NonceStore};
12use serde::Serialize;
13
14use crate::alg::CoseMode;
15use crate::keys::{CoseSigner, CoseVerifierResolver};
16use crate::seal::PAYLOAD_CAPACITY_HINT;
17use cratestack_core::RequestNonce;
18
19pub use builder::CoseEnvelopeBuilder;
20use builder::{Clock, CtiSource, NonceSource};
21
22/// Which side of the exchange an envelope is on.
23///
24/// `CratestackEnvelope` has one `seal` and one `open`, and which message
25/// each handles depends on the side: a server opens requests and seals
26/// responses, a client seals requests and opens responses. The role is
27/// fixed at construction rather than inferred from the [`Binding`] (a
28/// response binding carries a `ResponseBinding`), so that a
29/// router that builds the wrong binding gets a `500` instead of silently
30/// skipping the replay checks a request needs. The typed methods
31/// ([`CoseEnvelope::open_request`] and the rest) name the direction
32/// explicitly and ignore the role.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34pub enum CoseRole {
35    /// Opens requests (with the replay checks), seals responses.
36    Server,
37    /// Seals requests (with `iat` and a fresh `cti`), opens responses.
38    Client,
39}
40
41/// A COSE_Sign1 or COSE_Mac0 envelope (ADR 0006 §§1, 3-5).
42///
43/// Cheap to clone: the configuration sits behind one `Arc`. Build it with
44/// [`CoseEnvelope::server`] or [`CoseEnvelope::client`].
45///
46/// **Errors**, for every method (§10): a failed verification is the coarse
47/// `CratestackError::Unauthorized`; a failing backend (key resolver, nonce
48/// store, signer) is `CratestackError::Internal`, a `500`; and **local
49/// misuse** (a binding whose shape does not fit the call, an empty
50/// `audience`, a request opened without a nonce store, a clock or `cti`
51/// source returning nonsense, a signer returning a signature of the wrong
52/// length, a codec whose `encode_into` does not append) is also
53/// `CratestackError::Internal`. Misuse depends only on local state, never
54/// on the received bytes, so a `500` for it reveals nothing about a
55/// message. A codec error from a `*_value` method is returned as the codec
56/// reported it.
57#[derive(Clone)]
58pub struct CoseEnvelope {
59    pub(crate) inner: Arc<Inner>,
60}
61
62pub(crate) struct Inner {
63    pub(crate) role: CoseRole,
64    pub(crate) mode: CoseMode,
65    pub(crate) signer: Arc<dyn CoseSigner>,
66    pub(crate) resolver: Arc<dyn CoseVerifierResolver>,
67    pub(crate) nonce_store: Option<Arc<dyn NonceStore>>,
68    pub(crate) skew_secs: u64,
69    pub(crate) clock: Clock,
70    pub(crate) cti: CtiSource,
71    pub(crate) nonce: NonceSource,
72}
73
74impl Inner {
75    fn debug_fields(&self, out: &mut fmt::DebugStruct<'_, '_>) -> fmt::Result {
76        out.field("role", &self.role)
77            .field("mode", &self.mode)
78            .field("alg", &self.signer.alg())
79            .field("kid", &self.signer.kid())
80            .field("skew_secs", &self.skew_secs)
81            .field("nonce_store", &self.nonce_store.is_some())
82            .finish_non_exhaustive()
83    }
84}
85
86impl CoseEnvelope {
87    /// The side this envelope is on.
88    pub fn role(&self) -> CoseRole {
89        self.inner.role
90    }
91
92    /// The message structure this envelope emits and accepts. An envelope
93    /// accepts only its own mode's tag: a Sign1 envelope refuses a Mac0
94    /// message outright, whatever keys its resolver holds.
95    pub fn mode(&self) -> CoseMode {
96        self.inner.mode
97    }
98
99    /// A fresh `Cratestack-Nonce` for an outgoing request, from the
100    /// builder's nonce source. Fails with `CratestackError::Internal` if
101    /// that source fails.
102    pub fn request_nonce(&self) -> Result<RequestNonce, CratestackError> {
103        (self.inner.nonce)().map(RequestNonce::from_bytes)
104    }
105
106    /// Seal an encoded request payload for `bind` (a request binding:
107    /// `response` is `None`), with `iat` from the clock and a
108    /// fresh `cti`. The payload is copied into the message once; see
109    /// [`seal_request_value`](Self::seal_request_value) to avoid that.
110    pub async fn seal_request(
111        &self,
112        payload: &[u8],
113        bind: &Binding<'_>,
114    ) -> Result<Bytes, CratestackError> {
115        crate::seal::seal(&self.inner, bind, true, payload.len(), |out| {
116            out.extend_from_slice(payload);
117            Ok(())
118        })
119        .await
120    }
121
122    /// Seal an encoded response payload for `bind` (a response binding:
123    /// `response` names the request answered, its kind and the status).
124    /// Responses carry `alg` and `kid` only.
125    pub async fn seal_response(
126        &self,
127        payload: &[u8],
128        bind: &Binding<'_>,
129    ) -> Result<Bytes, CratestackError> {
130        crate::seal::seal(&self.inner, bind, false, payload.len(), |out| {
131            out.extend_from_slice(payload);
132            Ok(())
133        })
134        .await
135    }
136
137    /// [`seal_request`](Self::seal_request) for a value `codec` has not
138    /// encoded yet: it is encoded straight into the message buffer. The
139    /// bytes are identical to encoding first and sealing after. A codec
140    /// error is returned as the codec reported it.
141    pub async fn seal_request_value<C, T>(
142        &self,
143        codec: &C,
144        value: &T,
145        bind: &Binding<'_>,
146    ) -> Result<Bytes, CratestackError>
147    where
148        C: CratestackCodec,
149        T: Serialize + ?Sized,
150    {
151        crate::seal::seal(&self.inner, bind, true, PAYLOAD_CAPACITY_HINT, |out| {
152            codec.encode_into(value, out)
153        })
154        .await
155    }
156
157    /// [`seal_response`](Self::seal_response) for a value, encoded in
158    /// place as [`seal_request_value`](Self::seal_request_value) does.
159    pub async fn seal_response_value<C, T>(
160        &self,
161        codec: &C,
162        value: &T,
163        bind: &Binding<'_>,
164    ) -> Result<Bytes, CratestackError>
165    where
166        C: CratestackCodec,
167        T: Serialize + ?Sized,
168    {
169        crate::seal::seal(&self.inner, bind, false, PAYLOAD_CAPACITY_HINT, |out| {
170            codec.encode_into(value, out)
171        })
172        .await
173    }
174}
175
176impl fmt::Debug for CoseEnvelope {
177    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178        self.inner.debug_fields(&mut f.debug_struct("CoseEnvelope"))
179    }
180}