gose/cose/sign

COSE_Sign multi-signer signing and verification (RFC 9052).

Example

import gose/algorithm
import gose/cose/sign
import gose/key
import kryptos/ec

let payload = <<"hello":utf8>>
let k1 = key.generate_ec(ec.P256)
let k2 = key.generate_ec(ec.P384)

let assert Ok(body) =
  sign.new(payload:)
  |> sign.sign(algorithm.Ecdsa(algorithm.EcdsaP256), key: k1)
let assert Ok(body) =
  sign.sign(body, algorithm.Ecdsa(algorithm.EcdsaP384), key: k2)
let signed = sign.assemble(body)

let data = sign.serialize(signed)
let assert Ok(parsed) = sign.parse(data)
let assert Ok(verifier) =
  sign.verifier(algorithm.Ecdsa(algorithm.EcdsaP256), keys: [k1])
let assert Ok(Nil) = sign.verify(verifier, parsed)

Building, signing, assembling

Body(state) uses a phantom to enforce ordering:

  1. new(payload:) returns Body(Building).
  2. with_* builders only accept Body(Building), so configuration happens before any signature is computed.
  3. sign(body, alg, key) computes a signature over the body and returns Body(Signed). Calling sign from either state yields Signed, so subsequent signers just chain: |> sign(_, alg2, key2).
  4. assemble(body) finalizes a Body(Signed) into Sign(Signed).

Because builders require Body(Building), mutating the body after any sign call is a compile error. The body each signer signed over matches the body that gets serialized on the wire.

Algorithm Pinning

Each verifier is pinned to a single signature algorithm. The matched signer’s protected header alg must match the verifier’s expected algorithm.

Types

Outer message body holding body-level protected and unprotected headers, the payload, and any accumulated signatures.

pub opaque type Body(state)

Phantom type: body under construction, no signatures yet.

pub type Building

A COSE_Sign message parameterized by signing state.

pub opaque type Sign(state)

Phantom type: body with at least one signature, or a finalized message.

pub type Signed

Holds an algorithm and set of keys for verifying a COSE_Sign message.

pub opaque type Verifier

Values

pub fn assemble(body: Body(Signed)) -> Sign(Signed)

Finalize a signed body into a serializable COSE_Sign message.

pub fn content_type(
  message: Sign(Signed),
) -> Result(cose.ContentType, gose.GoseError)

Extract the content type from the body-level headers.

pub fn critical(
  message: Sign(Signed),
) -> Result(List(Int), gose.GoseError)

Extract the critical header labels from the body-level headers.

pub fn kid(
  message: Sign(Signed),
) -> Result(BitArray, gose.GoseError)

Extract the key ID from the body-level headers.

pub fn new(payload payload: BitArray) -> Body(Building)

Create a new body pinned to the payload all signers will sign.

pub fn parse(
  data: BitArray,
) -> Result(Sign(Signed), gose.GoseError)

Decode a CBOR-encoded COSE_Sign message, accepting both tagged and untagged forms.

pub fn payload(message: Sign(Signed)) -> Result(BitArray, Nil)

Return the payload from a signed message. Returns Error(Nil) if detached.

pub fn protected_headers(
  message: Sign(Signed),
) -> List(cose.Header)

Return the raw body-level protected headers.

pub fn serialize(message: Sign(Signed)) -> BitArray

Encode a signed message as an untagged CBOR COSE_Sign array.

pub fn serialize_tagged(message: Sign(Signed)) -> BitArray

Encode a signed message as a CBOR-tagged (tag 98) COSE_Sign structure.

pub fn sign(
  body: Body(state),
  alg alg: algorithm.DigitalSignatureAlg,
  key key: key.Key(BitArray),
) -> Result(Body(Signed), gose.GoseError)

Compute a per-signer signature over the body’s payload and append it to the body. Transitions the body to Signed state, preventing further with_* mutations at compile time.

pub fn unprotected_headers(
  message: Sign(Signed),
) -> List(cose.Header)

Return the raw body-level unprotected headers.

pub fn verifier(
  alg: algorithm.DigitalSignatureAlg,
  keys keys: List(key.Key(BitArray)),
) -> Result(Verifier, gose.GoseError)

Build a verifier pinned to a single signature algorithm and one or more keys.

pub fn verify(
  verifier: Verifier,
  message: Sign(Signed),
) -> Result(Nil, gose.GoseError)

Verify the first matching signer’s signature.

pub fn verify_detached(
  verifier: Verifier,
  message message: Sign(Signed),
  payload payload: BitArray,
) -> Result(Nil, gose.GoseError)

Verify a detached-payload message.

pub fn verify_detached_with_aad(
  verifier: Verifier,
  message message: Sign(Signed),
  payload payload: BitArray,
  aad aad: BitArray,
) -> Result(Nil, gose.GoseError)

Verify a detached-payload message with external AAD.

pub fn verify_with_aad(
  verifier: Verifier,
  message message: Sign(Signed),
  aad aad: BitArray,
) -> Result(Nil, gose.GoseError)

Verify with externally-supplied AAD.

pub fn with_aad(
  body: Body(Building),
  aad aad: BitArray,
) -> Body(Building)

Set external additional authenticated data (AAD) for the signing operation.

pub fn with_content_type(
  body: Body(Building),
  ct ct: cose.ContentType,
) -> Body(Building)

Add a content type to the body’s unprotected headers.

RFC 9052 permits either bucket. Signed messages place it in unprotected, consistent with with_kid.

pub fn with_critical(
  body: Body(Building),
  labels labels: List(Int),
) -> Body(Building)

Add critical header labels to the body’s protected headers.

pub fn with_detached(body: Body(Building)) -> Body(Building)

Mark the message for detached payload. The payload captured on new(payload:) is still covered by each signature, but assemble omits it from the serialized output.

pub fn with_kid(
  body: Body(Building),
  kid kid: BitArray,
) -> Body(Building)

Add a key ID to the body’s unprotected headers.

Search Document