Skip to main content

cratestack_cose/claims/
sign.rs

1//! Signing a claim object.
2
3use super::error::ClaimsError;
4use super::header;
5use super::types::CwtClaims;
6use crate::alg::CoseMode;
7use crate::cbor::write::{self, EMPTY_MAP, MAJOR_ARRAY, MAJOR_TAG};
8use crate::keys::{CoseSigner, esp256_low_s};
9use crate::tbs::Tbs;
10use crate::thumbprint::KID_LEN;
11
12/// Sign `payload` as a claim object: a tagged `COSE_Sign1` (tag 18) whose
13/// protected header is `{1: alg, 4: kid, ? 15: claims, 16: typ}`, whose
14/// unprotected header is empty and whose payload is embedded.
15///
16/// - `typ` (RFC 9596, label 16) names what the object is. It is signed, and
17///   [`open_claims`](crate::open_claims) refuses any other, so an object
18///   minted for one purpose cannot be replayed as another. It is also what
19///   keeps a claim object from opening as a transport envelope, and the
20///   reverse. Non-empty, at most [`MAX_TYP_LEN`](crate::MAX_TYP_LEN) bytes.
21/// - `claims` are written only when present; see [`CwtClaims`] for the
22///   bounds.
23/// - `external_aad` is signed but not sent: the opener must supply the same
24///   bytes. Put in it what the object is bound to but does not carry, such
25///   as a tenant or a purpose; empty is fine.
26///
27/// The encoding is deterministic (RFC 8949 ยง4.2.1) and an ESP256 signature
28/// is low-`s`, the rules the transport envelope enforces. `signer` must be
29/// a signature signer (Ed25519 or ESP256); an HMAC signer is refused with
30/// [`ClaimsError::Misuse`]. Nothing is read from a clock: `exp` and `iat`
31/// are whatever the caller put in.
32///
33/// ```
34/// use cratestack_cose::{CwtClaims, Ed25519Signer, sign_claims};
35///
36/// # tokio::runtime::Builder::new_current_thread().build().unwrap().block_on(async {
37/// let signer = Ed25519Signer::from_seed(&[7; 32]);
38/// let claims = CwtClaims { exp: Some(1_790_003_600), ..CwtClaims::default() };
39/// let bytes = sign_claims(&signer, "application/vnd.example.grant+cwt", &claims, b"grant", b"")
40///     .await
41///     .unwrap();
42/// assert_eq!(bytes[0], 0xd2); // tag 18
43/// # });
44/// ```
45pub async fn sign_claims(
46    signer: &dyn CoseSigner,
47    typ: &str,
48    claims: &CwtClaims,
49    payload: &[u8],
50    external_aad: &[u8],
51) -> Result<Vec<u8>, ClaimsError> {
52    let alg = signer.alg();
53    if alg.mode() != CoseMode::Sign1 {
54        return Err(ClaimsError::Misuse(
55            "a claim object needs a signature algorithm, not a MAC",
56        ));
57    }
58    if signer.kid().len() != KID_LEN {
59        return Err(ClaimsError::Misuse("the signer's kid must be 8 bytes"));
60    }
61    if !header::typ_ok(typ) {
62        return Err(ClaimsError::Misuse("typ must be 1 to MAX_TYP_LEN bytes"));
63    }
64    header::check_claims(claims).map_err(ClaimsError::Misuse)?;
65
66    let protected = header::encode(alg, signer.kid(), typ, claims);
67    let tbs = Tbs {
68        mode: CoseMode::Sign1,
69        protected: &protected,
70        external_aad,
71        payload,
72    };
73    let signature = match tbs.with_chunks(|chunks| signer.sign_chunks(chunks)) {
74        Some(signed) => signed,
75        None => signer.sign(&tbs.assemble()).await,
76    }
77    .map_err(ClaimsError::Signer)?;
78    if signature.len() != alg.signature_len() {
79        return Err(ClaimsError::Misuse(
80            "the signer returned a signature of the wrong length",
81        ));
82    }
83    let signature = if alg == crate::CoseAlg::Esp256 {
84        esp256_low_s(&signature).ok_or(ClaimsError::Misuse(
85            "the signer returned an invalid ESP256 signature",
86        ))?
87    } else {
88        signature
89    };
90
91    let mut out = Vec::with_capacity(
92        2 + write::bstr_len(protected.len())
93            + 1
94            + write::bstr_len(payload.len())
95            + write::bstr_len(signature.len()),
96    );
97    write::head(&mut out, MAJOR_TAG, CoseMode::Sign1.tag());
98    write::head(&mut out, MAJOR_ARRAY, 4);
99    write::bstr(&mut out, &protected);
100    out.push(EMPTY_MAP);
101    write::bstr(&mut out, payload);
102    write::bstr(&mut out, &signature);
103    Ok(out)
104}