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}