Skip to main content

CoseSigner

Trait CoseSigner 

Source
pub trait CoseSigner: MaybeSendSync + 'static {
    // Required methods
    fn alg(&self) -> CoseAlg;
    fn kid(&self) -> &[u8] ⓘ;
    fn sign<'life0, 'life1, 'async_trait>(
        &'life0 self,
        to_be_signed: &'life1 [u8],
    ) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, CratestackError>> + Send + 'async_trait>>
       where 'life0: 'async_trait,
             'life1: 'async_trait,
             Self: 'async_trait;

    // Provided method
    fn sign_chunks(
        &self,
        to_be_signed: &[&[u8]],
    ) -> Option<Result<Vec<u8>, CratestackError>> { ... }
}
Expand description

Produces signatures (or MAC tags) without handing out the key.

Target-split (cratestack#1007, the same split as the client’s RequestAuthorizer): natively the trait is Send + Sync and its futures are Send, exactly as before. On wasm32 it is neither, because a signer there is a WebCrypto call or a JS callback, and a JsFuture is never Send. An implementation that builds for both targets writes #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] and #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))].

KeyProvider::resolve_signing_key returns raw key bytes, which fits HMAC but not an asymmetric key that lives in an HSM or a KMS (§1: “keys are signed with, not exported”). A signer is asked to sign the complete Sig_structure / MAC_structure and nothing else.

#[async_trait] rather than impl Future: signers are held as Arc<dyn CoseSigner> so one envelope type serves every key backend, and a remote signer is a network call anyway, next to which the boxed future is noise.

The envelope checks what a signer returns: a kid that is not 8 bytes, an algorithm that does not fit the envelope’s mode, or a signature whose length is not CoseAlg::signature_len is refused with a 500 before anything reaches the wire. An ESP256 signature is normalised to low-s.

Required Methods§

Source

fn alg(&self) -> CoseAlg

The algorithm every signature from this signer uses.

Source

fn kid(&self) -> &[u8] ⓘ

The key’s kid: the first 8 bytes of its RFC 9679 thumbprint (§3).

Source

fn sign<'life0, 'life1, 'async_trait>( &'life0 self, to_be_signed: &'life1 [u8], ) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, CratestackError>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, Self: 'async_trait,

Sign (or MAC) to_be_signed. For HMAC 256/64 this returns the truncated 8-byte tag. An error is a backend failure (a 500).

Provided Methods§

Source

fn sign_chunks( &self, to_be_signed: &[&[u8]], ) -> Option<Result<Vec<u8>, CratestackError>>

Sign the concatenation of to_be_signed without it ever being built in one buffer, or None to have the envelope build it and call sign instead (the default).

This is how the in-process signers (HMAC, ESP256 and Ed25519) keep the payload in the one place seal_value encoded it (maintainer decisions on cratestack#1005): each hashes its input (Ed25519 twice), so they can be fed the structure’s pieces, which lie in the output buffer, on the stack and in the header. The design has to stay dyn-compatible (signers are Arc<dyn CoseSigner>), which rules out a generic “feed this hasher” method; a slice of slices is the dyn-compatible form of a stream of bytes. It is a provided method, so adding it broke no signer. It is synchronous because it exists for in-process signers only: a remote signer needs the bytes (or a digest) on the wire and keeps None, so a KMS or HSM signer is handed the contiguous structure.

An override must return exactly what sign returns for the concatenated bytes; the shared vectors check that for the shipped signers.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§