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 ◀──────┘CoseEnvelopeimplementscratestack_core::CratestackEnvelope, and also offers typed methods (CoseEnvelope::open_requestand the rest) that need noCratestackContext, for the axum layer (cratestack#1006), which runs before a context exists.CoseSignerandCoseVerifierResolverare the key seams: a KMS or HSM signs without exporting the key, and a resolver may return several candidates for onekid.external_aaddefines the binding (§4), which includes the receiving service’saudience(never empty, and distinct from the audience the service seals its own outbound requests for);request_digestandrequest_digest_unsignedbind a response to its request, the latter through the client’sRequestNonce(Cratestack-Nonce), and each returns its digest together with theRequestKindthe response AAD binds;thumbprintthe RFC 9679 key ids (§3).KeyProviderMacKeysturnscratestack_core::KeyProvidersecrets 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 privatecbormodule’s doc);cosetis a dev-dependency that the tests check the bytes against. - Algorithms are the closed
CoseAlg, notcoset::iana::Algorithm. - The payload is encoded in place only on the
seal_valuepath (CratestackEnvelope::seal_value,CoseEnvelope::seal_request_valueandCoseEnvelope::seal_response_value).sealreceives 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 defaultCoseSigner::sign_chunksand is handed the structure in one buffer. Verification never builds it contiguously. - ESP256 signatures are low-
sonly: the sealer normalises them, the opener rejects a highs, so no third party can re-spell a signed message.
Modules§
- auth
cratestack-authintegration: theauthCargo 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
kidderived from them (ADR 0006 §3).
Structs§
- Call
Binding - Everything one call binds, owned.
- Cose
Envelope - A COSE_Sign1 or COSE_Mac0 envelope (ADR 0006 §§1, 3-5).
- Cose
Envelope Builder - Configuration for a
CoseEnvelope; seeCoseEnvelope::serverandCoseEnvelope::client. Nothing is checked untilbuild. - Cose
Verify Key - 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 areSomeare written. - Ed25519
Signer - An Ed25519 (
-19) signer. Ed25519 is deterministic, so the shared vectors compare its output byte for byte. - External
Signer - 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.
- Hmac
Secret - An HMAC secret of at least
MIN_HMAC_SECRET_LENbytes. - Hmac
Signer - The in-process COSE_Mac0 signer: service to service inside one trust
domain (§3). Its
kidis the thumbprint prefix of the secret. - KeyProvider
MacKeys - A fixed set of HMAC keys, loaded from a
KeyProviderby string id. - Opened
- A message that verified.
- Opened
Claims - A claim object whose signature verified.
- P256
Signer - An ESP256 (
-9) signer from a 32-byte private scalar. - Registry
Verifier Resolver - 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
StaticVerifierResolveris fixed at construction). Holds every key type the verifier accepts: Ed25519, ESP256 and the two HMAC algorithms. - Request
Nonce - A
Cratestack-Nonce: 16 bytes, chosen by the client. - Static
Verifier Resolver - 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 implementsCoseVerifierResolveritself.
Enums§
- Claims
Error - A failure of
sign_claimsoropen_claims. - CoseAlg
- A COSE algorithm this crate signs or verifies with.
- Cose
Mode - The unary message structure: COSE_Sign1 or COSE_Mac0.
- Cose
Role - 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
HmacEnvelopeand 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
ctithe 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 staleiatand a replayedctiall produceCratestackError::Unauthorizedwith this text, byte for byte.
Traits§
- Cose
Signer - Produces signatures (or MAC tags) without handing out the key.
- Cose
Verifier Resolver - Finds the keys that may have produced a message.
- Maybe
Send Sendnatively, nothing onwasm32.- Maybe
Send Sync Send + Syncnatively, nothing onwasm32.
Functions§
- contract_
header_ value - The
Cratestack-Contractheader 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 theSig_structure/MAC_structureasexternal_aad. Fails withCratestackError::Internal(misuse) for an emptyaudience. - open_
claims - Open a claim object: parse it strictly, check its
typ, and verify its signature with the keyresolverreturns for itskid. - random_
request_ nonce - A fresh
Cratestack-Nonce: 16 bytes from the operating system’s CSPRNG. Fails withCratestackError::Internalif 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
payloadas a claim object: a taggedCOSE_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.