Skip to main content

Crate cose

Crate cose 

Source
Expand description

cratestack-cose: the COSE envelope of ADR 0006, unary part (P0).

A signed body is the codec’s output wrapped as a COSE_Sign1 (tag 18) or COSE_Mac0 (tag 17), bound to the request through external AAD that is rebuilt on both sides and never sent:

typed value ──CborCodec──▶ payload bytes ──CoseEnvelope──▶ COSE_Sign1 / COSE_Mac0
                                ▲                               │
                                └──────── verified as-is ◀──────┘
  • CoseEnvelope implements cratestack_core::CratestackEnvelope, and also offers typed methods (CoseEnvelope::open_request and the rest) that need no CratestackContext, for the axum layer (cratestack#1006), which runs before a context exists.
  • CoseSigner and CoseVerifierResolver are the key seams: a KMS or HSM signs without exporting the key, and a resolver may return several candidates for one kid.
  • external_aad defines the binding (§4), which includes the receiving service’s audience (never empty, and distinct from the audience the service seals its own outbound requests for); request_digest and request_digest_unsigned bind a response to its request, the latter through the client’s RequestNonce (Cratestack-Nonce), and each returns its digest together with the RequestKind the response AAD binds; thumbprint the RFC 9679 key ids (§3).
  • KeyProviderMacKeys turns cratestack_core::KeyProvider secrets into Mac0 keys.

Wire format: the server layer (cratestack#1006) and the Rust client (cratestack#1007) ship together in the release that freezes binding version 1 (#1082); from it on the wire format changes only with a new BINDING_VERSION.

Algorithms: Ed25519 (-19, the default) and ESP256 (-9) for Sign1, HMAC 256/64 (4) and 256/256 (5) for Mac0. Nothing else is accepted, including the deprecated -8 and -7 (see CoseAlg).

Errors (§10): every failed check is the same CratestackError::Unauthorized carrying UNAUTHENTICATED; a failing key resolver, nonce store or signer is CratestackError::Internal, and so is local misuse (see CoseEnvelope).

The auth feature (off by default) adds cratestack_cose::auth: the cratestack-auth adapters (ServiceSigningKey as a signer, DeviceKeyResolver as a resolver), the bridge to its Redis nonce store, and the COSE enrolment challenge code moved out of cratestack-auth. It is this crate’s only edge to cratestack-auth.

Claim objects (claims): sign_claims / open_claims produce and verify a self-contained COSE_Sign1 with a mandatory typ, RFC 9597 CWT claims and no freshness or replay check, for a payload signed offline and delivered later. Unrelated to the envelope: neither opens as the other.

Not here yet: chain streams (P1) and window replay (P2).

§Where this departs from the ADR’s sketch

  • The outer structure is emitted and parsed by hand, not with coset (see the private cbor module’s doc); coset is a dev-dependency that the tests check the bytes against.
  • Algorithms are the closed CoseAlg, not coset::iana::Algorithm.
  • The payload is encoded in place only on the seal_value path (CratestackEnvelope::seal_value, CoseEnvelope::seal_request_value and CoseEnvelope::seal_response_value). seal receives bytes that are already encoded and copies them into the message once.
  • For in-process signers (HMAC, ESP256 and Ed25519) the signature is computed over the to-be-signed structure in pieces, with no copy of the payload; Ed25519 runs both PureEdDSA passes over the pieces (see Ed25519Signer). A KMS or HSM signer keeps the default CoseSigner::sign_chunks and is handed the structure in one buffer. Verification never builds it contiguously.
  • ESP256 signatures are low-s only: the sealer normalises them, the opener rejects a high s, so no third party can re-spell a signed message.

Modules§

auth
cratestack-auth integration: the auth Cargo feature (ADR 0006, “Decisions taken while scoping P0”; cratestack#1005 part B).
claims
Claim objects: a signed payload that outlives a transport envelope.
thumbprint
RFC 9679 COSE Key Thumbprints, and the 8-byte kid derived from them (ADR 0006 §3).

Structs§

CallBinding
Everything one call binds, owned.
CoseEnvelope
A COSE_Sign1 or COSE_Mac0 envelope (ADR 0006 §§1, 3-5).
CoseEnvelopeBuilder
Configuration for a CoseEnvelope; see CoseEnvelope::server and CoseEnvelope::client. Nothing is checked until build.
CoseVerifyKey
A key a message may be verified with, bound to exactly one algorithm.
CwtClaims
The CWT claims (RFC 8392 §3.1) a claim object may carry in protected label 15 (RFC 9597). Only the claims that are Some are written.
Ed25519Signer
An Ed25519 (-19) signer. Ed25519 is deterministic, so the shared vectors compare its output byte for byte.
ExternalSigner
A signer that hands the bytes to a callback: a key in the Android Keystore or the iOS Secure Enclave, a KMS, a hardware token.
HmacSecret
An HMAC secret of at least MIN_HMAC_SECRET_LEN bytes.
HmacSigner
The in-process COSE_Mac0 signer: service to service inside one trust domain (§3). Its kid is the thumbprint prefix of the secret.
KeyProviderMacKeys
A fixed set of HMAC keys, loaded from a KeyProvider by string id.
Opened
A message that verified.
OpenedClaims
A claim object whose signature verified.
P256Signer
An ESP256 (-9) signer from a 32-byte private scalar.
RegistryVerifierResolver
A set of verification keys that can be registered and revoked while the server runs: the resolver for devices or peers that enrol after start-up (the immutable StaticVerifierResolver is fixed at construction). Holds every key type the verifier accepts: Ed25519, ESP256 and the two HMAC algorithms.
RequestNonce
A Cratestack-Nonce: 16 bytes, chosen by the client.
StaticVerifierResolver
A fixed set of verification keys, indexed by their kid. For tests and for in-process use (a service that knows its peers’ keys at start-up). A device-key database or a published COSE_KeySet implements CoseVerifierResolver itself.

Enums§

ClaimsError
A failure of sign_claims or open_claims.
CoseAlg
A COSE algorithm this crate signs or verifies with.
CoseMode
The unary message structure: COSE_Sign1 or COSE_Mac0.
CoseRole
Which side of the exchange an envelope is on.

Constants§

BINDING_VERSION
The binding version, the first array element. Q5’s escape hatch: a future binding scheme gets a new number instead of a new wire format.
DEFAULT_SKEW_SECS
The default clock skew, the same 300 s HmacEnvelope and the signed-request verifier use (ENVELOPE_DEFAULT_CLOCK_SKEW_SECS).
MAX_AUD_LEN
The longest aud (bytes of UTF-8) a claim object carries.
MAX_CTI_LEN
The longest cti (bytes) a claim object carries.
MAX_TYP_LEN
The longest typ (bytes of UTF-8) a claim object carries.
MIN_HMAC_SECRET_LEN
The shortest HMAC secret accepted, in bytes.
NONCE_HEADER
The request header that carries the nonce.
NONCE_HEADER_VALUE_LEN
The header value’s exact length: 16 bytes as unpadded base64url.
RANDOM_CTI_LEN
The length of the random cti the default source produces (§5).
REQUEST_NONCE_LEN
Nonce length in bytes.
UNAUTHENTICATED
The one message every failed envelope verification carries (ADR 0006 §10): the peer must never learn which check failed. A malformed body, an unknown kid, a disallowed algorithm, a bad signature, a stale iat and a replayed cti all produce CratestackError::Unauthorized with this text, byte for byte.

Traits§

CoseSigner
Produces signatures (or MAC tags) without handing out the key.
CoseVerifierResolver
Finds the keys that may have produced a message.
MaybeSend
Send natively, nothing on wasm32.
MaybeSendSync
Send + Sync natively, nothing on wasm32.

Functions§

contract_header_value
The Cratestack-Contract header value for an op contract digest: the first 8 bytes as 11 characters of unpadded base64url. The header is not bound; it tells a server holding several accepted digests which one the request was sealed under.
external_aad
Encode the external AAD for bind: the bytes that go into the Sig_structure / MAC_structure as external_aad. Fails with CratestackError::Internal (misuse) for an empty audience.
open_claims
Open a claim object: parse it strictly, check its typ, and verify its signature with the key resolver returns for its kid.
random_request_nonce
A fresh Cratestack-Nonce: 16 bytes from the operating system’s CSPRNG. Fails with CratestackError::Internal if it is unavailable.
request_digest
The request digest a response binding carries when the request was signed: SHA-256 over the request body exactly as it travelled, the whole COSE message (tag, headers, signature and all), marked RequestKind::Signed. The caller passes the received body, never a re-encoding of it.
request_digest_unsigned
The request digest of a response to an unsigned request: SHA-256 over the 16 nonce bytes followed by the request payload exactly as received (empty for a bodiless request), marked RequestKind::Unsigned. The nonce has a fixed length, so the split between the two is unambiguous.
sign_claims
Sign payload as a claim object: a tagged COSE_Sign1 (tag 18) whose protected header is {1: alg, 4: kid, ? 15: claims, 16: typ}, whose unprotected header is empty and whose payload is embedded.