signer_core/
signer_jwt.rs1use 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 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 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 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}