Skip to main content

ServerEnvelope

Trait ServerEnvelope 

Source
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_request is the only way a request reaches the router as “signed”, and it is called for every request whose Content-Type is application/cose, whether or not is_envelope_content_type says so.
  • An Err(CratestackError::Unauthorized(_)) becomes the layer’s own coarse, unsigned 401; the message is dropped. Any other Err is a 500 whose detail is logged, never sent.
  • The binding passed in is built by the layer. The response binding for seal_response reuses the request’s values and adds the request digest the layer computed (SHA-256 of the exact bytes handed to open_request) and the status.
  • The SealContext open_request returned comes back, untouched, to the seal_response of the same request, and to no other; a response to an unsigned request gets SealContext::empty.
  • A Sealed whose media type is not application/cose (any parameters) or one this envelope claims through is_envelope_content_type is never sent: the layer answers with an unsigned 500 instead, 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 Unauthorized for 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 kid alone).

§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§

Source

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).

Source

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).

Source

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§

Source

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.

Source

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.

Source§

fn media_type(&self) -> &'static str

Source§

fn is_envelope_content_type(&self, content_type: &str) -> bool

Source§

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, Arc<T>: 'async_trait,

Source§

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, Arc<T>: 'async_trait,

Source§

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, Arc<T>: 'async_trait,

Implementors§