signer_core/
signer_jwt.rs

1use anyhow::bail;
2use base64::{prelude::BASE64_URL_SAFE, Engine};
3use serde::{Deserialize, Serialize};
4
5use crate::{
6  signer_signature::SignerSignature, signer_user::SignerUser, SignerUserPublic,
7};
8
9#[derive(Debug, Serialize, Deserialize)]
10pub struct SignerJWT {
11  pub header: SignerJWTHeader,
12  pub claims: SignerJWTClaims,
13}
14
15#[derive(Debug, Serialize, Deserialize)]
16pub struct SignerJWTHeader {
17  pub typ: String,
18  pub alg: String,
19  pub jwk: SignerJWK,
20}
21
22#[derive(Debug, Serialize, Deserialize)]
23pub struct SignerJWTClaims {
24  pub aud: String,
25  pub jti: String,
26  pub iat: i64,
27  pub exp: i64,
28  pub nbf: i64,
29  pub iss: String,
30  pub sub: String,
31  pub user_public: SignerUserPublic,
32  pub user_public_signature: String,
33  pub extra: Option<String>,
34}
35
36#[derive(Debug, Serialize, Deserialize)]
37pub struct SignerJWK {
38  pub kty: String,
39  pub alg: String,
40  pub crv: String,
41  pub x: String,
42}
43
44impl SignerJWT {
45  pub fn new(header: SignerJWTHeader, claims: SignerJWTClaims) -> Self {
46    Self { header, claims }
47  }
48
49  pub fn decode_unverify(str: &str) -> anyhow::Result<Self> {
50    let split: Vec<&str> = str.split(".").collect();
51    let header: SignerJWTHeader =
52      serde_json::from_slice(&BASE64_URL_SAFE.decode(split[0].as_bytes())?)?;
53    let claims: SignerJWTClaims =
54      serde_json::from_slice(&BASE64_URL_SAFE.decode(split[1].as_bytes())?)?;
55
56    Ok(SignerJWT::new(header, claims))
57  }
58
59  pub fn decode(str: &str) -> anyhow::Result<Self> {
60    let split: Vec<&str> = str.split(".").collect();
61    let header: SignerJWTHeader =
62      serde_json::from_slice(&BASE64_URL_SAFE.decode(split[0].as_bytes())?)?;
63    let claims: SignerJWTClaims =
64      serde_json::from_slice(&BASE64_URL_SAFE.decode(split[1].as_bytes())?)?;
65
66    let base = [split[0], split[1]].join(".");
67    let sig_bytes = split[2];
68
69    // verify signature
70    let sig = SignerSignature {
71      bytes: sig_bytes.to_string(),
72      pub_key: header.jwk.x.clone(),
73    };
74    sig.verify(base.as_bytes())?;
75
76    // verify claims
77    if header.jwk.x != claims.user_public.pub_key {
78      bail!(
79        "invalid claims, header key is not equal to claims user public key"
80      );
81    }
82    claims.verify()?;
83
84    Ok(SignerJWT::new(header, claims))
85  }
86
87  pub fn encode(&self, u: &SignerUser) -> anyhow::Result<String> {
88    let base = [
89      BASE64_URL_SAFE.encode(serde_json::to_string(&self.header)?.as_bytes()),
90      BASE64_URL_SAFE.encode(serde_json::to_string(&self.claims)?.as_bytes()),
91    ]
92    .join(".");
93
94    let sig = SignerSignature::create(u, base.as_bytes())?;
95
96    Ok([base, sig.bytes].join("."))
97  }
98}
99
100impl SignerJWTClaims {
101  pub fn default(signer: &SignerUser, aud: String, jti: String) -> Self {
102    let now = chrono::Local::now();
103    Self {
104      aud,
105      jti,
106      iat: now.timestamp_millis(),
107      exp: now
108        .checked_add_days(chrono::Days::new(7))
109        .unwrap()
110        .timestamp_millis(),
111      nbf: now.timestamp_millis(),
112      iss: format!(""),
113      sub: format!(""),
114      extra: None,
115      user_public: signer.public.clone(),
116      user_public_signature: base64::prelude::BASE64_URL_SAFE.encode(
117        signer
118          .public
119          .create_signature(signer)
120          .expect("create signature for signer public failed"),
121      ),
122    }
123  }
124
125  pub fn with_issuer(mut self, issuer: &str) -> Self {
126    self.iss = issuer.to_string();
127    self
128  }
129
130  pub fn with_expired_duration(
131    mut self,
132    expired_time: chrono::Duration,
133  ) -> Self {
134    self.exp = chrono::DateTime::from_timestamp_millis(self.iat)
135      .unwrap()
136      .checked_add_signed(expired_time)
137      .unwrap()
138      .timestamp_millis();
139    self
140  }
141
142  pub fn with_subject(mut self, subject: &str) -> Self {
143    self.sub = subject.to_string();
144    self
145  }
146
147  pub fn with_extra(mut self, extra: &impl Serialize) -> Self {
148    self.extra =
149      Some(serde_json::to_string(extra).expect("Failed to serialize extra"));
150    self
151  }
152
153  pub fn verify(&self) -> anyhow::Result<()> {
154    let now = chrono::Local::now();
155
156    if now.timestamp_millis() < self.nbf {
157      bail!("blocked by token not before field");
158    }
159
160    if now.timestamp_millis() > self.exp {
161      bail!("blocked by token expired field");
162    }
163
164    // validate user public
165    self.user_public.verify_signature(
166      &base64::prelude::BASE64_URL_SAFE.decode(&self.user_public_signature)?,
167    )?;
168
169    Ok(())
170  }
171}
172
173impl SignerJWTHeader {
174  pub fn default(u: &SignerUser) -> Self {
175    Self {
176      typ: format!("JWT"),
177      alg: format!("Schnorr SHA3-256"),
178      jwk: SignerJWK::default(u),
179    }
180  }
181}
182
183impl SignerJWK {
184  fn default(u: &SignerUser) -> Self {
185    Self {
186      kty: format!("OKP"),
187      alg: format!("schnorr"),
188      crv: format!("sr25519"),
189      x: u.public.pub_key.clone(),
190    }
191  }
192}