pub trait ServerEnvelope:
Send
+ Sync
+ 'static {
// Required methods
fn media_type(&self) -> &'static str;
fn open_request<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
body: Bytes,
bind: &'life1 Binding<'life2>,
) -> Pin<Box<dyn Future<Output = Result<OpenedRequest, CratestackError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait;
fn seal_response<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
payload: &'life1 [u8],
bind: &'life2 Binding<'life3>,
context: &'life4 SealContext,
) -> Pin<Box<dyn Future<Output = Result<Sealed, CratestackError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
Self: 'async_trait;
// Provided methods
fn is_envelope_content_type(&self, content_type: &str) -> bool { ... }
fn open_request_any<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
body: Bytes,
binds: &'life1 [Binding<'life2>],
) -> Pin<Box<dyn Future<Output = Result<(OpenedRequest, usize), CratestackError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait { ... }
}Expand description
What the layer needs from an envelope on the server side: open a request against its binding, seal a response against its binding.
cratestack_cose::CoseEnvelope implements it (feature cose).
Implement it yourself to put a different verifier behind the same layer:
an envelope whose keys live in an HSM or a KMS (for COSE, a custom
CoseSigner / CoseVerifierResolver on a CoseEnvelope is usually
less work), or the Sign1 + Mac0 composite of cratestack#1078. With the
envelope feature alone, no COSE crate is compiled.
It is not cratestack_core::CratestackEnvelope, which is Clone (so not
object-safe) and records its signer on a context; the layer runs before
any context exists and holds the envelope as dyn.
§What the layer guarantees whatever the implementation does
open_requestis the only way a request reaches the router as “signed”, and it is called for every request whoseContent-Typeisapplication/cose, whether or notis_envelope_content_typesays so.- An
Err(CratestackError::Unauthorized(_))becomes the layer’s own coarse, unsigned401; the message is dropped. Any otherErris a500whose detail is logged, never sent. - The binding passed in is built by the layer. The response binding for
seal_responsereuses the request’s values and adds the request digest the layer computed (SHA-256 of the exact bytes handed toopen_request) and the status. - The
SealContextopen_requestreturned comes back, untouched, to theseal_responseof the same request, and to no other; a response to an unsigned request getsSealContext::empty. - A
Sealedwhose media type is notapplication/cose(any parameters) or one this envelope claims throughis_envelope_content_typeis never sent: the layer answers with an unsigned500instead, so a response is never labelled as plain.
§What the implementation must do
- Verify everything the binding names (audience, method, route, path
parameters, query, schema digest, payload media type, bound headers)
and enforce replay protection before returning
Ok. The layer cannot check that a verifier verified. - Return
Unauthorizedfor every verification failure, and something else (Internal,Unavailable) only for a local or backend failure. - Return the payload exactly as signed, and the signer that actually
verified it (for COSE: the key’s full thumbprint, never the claimed
kidalone).
§Wrapping another envelope
Delegate through the trait’s fully qualified path,
ServerEnvelope::open_request(&self.inner, ..). CoseEnvelope also has
inherent open_request / seal_response methods, its typed API (they
return Opened and Bytes), and method-call syntax picks those over
the trait’s, so self.inner.open_request(body, bind) does not type-check
here (second-review item S-3).
use std::sync::atomic::{AtomicUsize, Ordering};
use bytes::Bytes;
use cratestack_axum::envelope_layer::{
OpenedRequest, SealContext, Sealed, ServerEnvelope, async_trait,
};
use cratestack_core::{Binding, CratestackError};
use cratestack_cose::CoseEnvelope;
/// The COSE envelope, counting the requests it opens.
struct Counted {
inner: CoseEnvelope,
opened: AtomicUsize,
}
#[async_trait]
impl ServerEnvelope for Counted {
fn media_type(&self) -> &'static str {
ServerEnvelope::media_type(&self.inner)
}
fn is_envelope_content_type(&self, content_type: &str) -> bool {
ServerEnvelope::is_envelope_content_type(&self.inner, content_type)
}
async fn open_request(
&self,
body: Bytes,
bind: &Binding<'_>,
) -> Result<OpenedRequest, CratestackError> {
self.opened.fetch_add(1, Ordering::Relaxed);
ServerEnvelope::open_request(&self.inner, body, bind).await
}
async fn seal_response(
&self,
payload: &[u8],
bind: &Binding<'_>,
context: &SealContext,
) -> Result<Sealed, CratestackError> {
ServerEnvelope::seal_response(&self.inner, payload, bind, context).await
}
}The method-call form reaches the inherent method instead, and fails:
async fn open(
inner: &CoseEnvelope,
body: Bytes,
bind: &Binding<'_>,
) -> Result<OpenedRequest, CratestackError> {
inner.open_request(body, bind).await
}Required Methods§
Sourcefn media_type(&self) -> &'static str
fn media_type(&self) -> &'static str
The envelope type this implementation claims, e.g.
application/cose; cose-type="cose-sign1", used for detection and
build validation: build() refuses a value that is not a valid
header value, or that the layer’s own detection would not recognise
on a request (application/cose, or a type
is_envelope_content_type
claims), so a client sending it is always opened, never forwarded.
The layer detects requests with those two checks, not with this
value, and does not send it: each response goes out as the
Sealed’s own media type, which lets a composite answer Mac0
with Mac0 (cratestack#1078).
Sourcefn open_request<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
body: Bytes,
bind: &'life1 Binding<'life2>,
) -> Pin<Box<dyn Future<Output = Result<OpenedRequest, CratestackError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
fn open_request<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
body: Bytes,
bind: &'life1 Binding<'life2>,
) -> Pin<Box<dyn Future<Output = Result<OpenedRequest, CratestackError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
Verify body (the request body exactly as received) against bind,
run the replay checks, and return the payload, the signer, and
anything the response to this request must know (the
SealContext, e.g. that a composite answered Mac0 with Mac0).
Sourcefn seal_response<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
payload: &'life1 [u8],
bind: &'life2 Binding<'life3>,
context: &'life4 SealContext,
) -> Pin<Box<dyn Future<Output = Result<Sealed, CratestackError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
Self: 'async_trait,
fn seal_response<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
payload: &'life1 [u8],
bind: &'life2 Binding<'life3>,
context: &'life4 SealContext,
) -> Pin<Box<dyn Future<Output = Result<Sealed, CratestackError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
Self: 'async_trait,
Seal payload (the response body the router produced, CBOR) for
bind, a response binding, with the context its request’s
open_request returned.
Provided Methods§
Sourcefn is_envelope_content_type(&self, content_type: &str) -> bool
fn is_envelope_content_type(&self, content_type: &str) -> bool
Whether content_type (a request’s Content-Type, or one Accept
entry, parameters included) names this envelope’s framing. Only
needed for a framing other than application/cose, which the layer
always recognises itself; the default recognises nothing more.
Sourcefn open_request_any<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
body: Bytes,
binds: &'life1 [Binding<'life2>],
) -> Pin<Box<dyn Future<Output = Result<(OpenedRequest, usize), CratestackError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
fn open_request_any<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
body: Bytes,
binds: &'life1 [Binding<'life2>],
) -> Pin<Box<dyn Future<Output = Result<(OpenedRequest, usize), CratestackError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
open_request against several candidate
bindings that must differ only in contract_sha, the op-contract
digest; any other difference (audience, route, method, path params,
query, payload type, bound headers) would widen what a signature
proves (the COSE envelope refuses it as misuse). Returns the opened
request and the index that verified. The default tries each in turn,
moving on only after the coarse 401, so it repeats parse and key
resolution per candidate; the COSE envelope overrides it to do both
once and repeat only the verification. Empty binds is Unauthorized.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".
Implementations on Foreign Types§
Source§impl<T> ServerEnvelope for Arc<T>where
T: ServerEnvelope + ?Sized,
An envelope shared with the rest of the application (or built once and
handed to several layers) is still an envelope.
impl<T> ServerEnvelope for Arc<T>where
T: ServerEnvelope + ?Sized,
An envelope shared with the rest of the application (or built once and handed to several layers) is still an envelope.