baseid_sd_jwt/
issuer.rs

1//! SD-JWT issuance.
2
3use baseid_crypto::jwt::{self, alg_to_str, JwtHeader};
4use baseid_crypto::signer::Signer;
5use serde_json::Value;
6
7use crate::disclosure::Disclosure;
8use crate::SdJwt;
9
10/// Builder for creating SD-JWTs with selective disclosure.
11pub struct SdJwtIssuer<'a> {
12    signer: &'a dyn Signer,
13    kid: String,
14    plain_claims: serde_json::Map<String, Value>,
15    sd_claims: Vec<(String, Value)>,
16}
17
18impl<'a> SdJwtIssuer<'a> {
19    /// Create a new SD-JWT issuer with the given signer and key ID.
20    pub fn new(signer: &'a dyn Signer, kid: &str) -> Self {
21        Self {
22            signer,
23            kid: kid.to_string(),
24            plain_claims: serde_json::Map::new(),
25            sd_claims: Vec::new(),
26        }
27    }
28
29    /// Add a plain (always-visible) claim.
30    pub fn add_plain_claim(mut self, name: &str, value: Value) -> Self {
31        self.plain_claims.insert(name.to_string(), value);
32        self
33    }
34
35    /// Add a selectively-disclosable claim.
36    pub fn add_sd_claim(mut self, name: &str, value: Value) -> Self {
37        self.sd_claims.push((name.to_string(), value));
38        self
39    }
40
41    /// Build the SD-JWT, creating disclosures and signing the JWT.
42    pub fn build(self) -> baseid_core::Result<SdJwt> {
43        let mut disclosures = Vec::new();
44        let mut sd_digests = Vec::new();
45
46        // Create a disclosure for each selectively-disclosable claim
47        for (name, value) in &self.sd_claims {
48            let disclosure = Disclosure::new(Some(name.clone()), value.clone());
49            let encoded = disclosure.encode()?;
50            let digest = disclosure.digest()?;
51            disclosures.push(encoded);
52            sd_digests.push(Value::String(digest));
53        }
54
55        // Build the JWT claims
56        let mut claims = self.plain_claims;
57        if !sd_digests.is_empty() {
58            claims.insert("_sd".to_string(), Value::Array(sd_digests));
59            claims.insert("_sd_alg".to_string(), Value::String("sha-256".to_string()));
60        }
61
62        let header = JwtHeader {
63            alg: alg_to_str(self.signer.algorithm()).to_string(),
64            typ: Some("sd-jwt".to_string()),
65            kid: Some(self.kid),
66            additional: serde_json::Map::new(),
67        };
68
69        let jwt = jwt::encode_jwt(&header, &Value::Object(claims), self.signer)?;
70
71        Ok(SdJwt {
72            jwt,
73            disclosures,
74            key_binding_jwt: None,
75        })
76    }
77}