1use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
6use baseid_core::error::{CryptoError, SerializationError};
7use baseid_core::types::SignatureAlgorithm;
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10
11use crate::signer::{Signer, Verifier};
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct JwtHeader {
16 pub alg: String,
18 #[serde(skip_serializing_if = "Option::is_none")]
20 pub typ: Option<String>,
21 #[serde(skip_serializing_if = "Option::is_none")]
23 pub kid: Option<String>,
24 #[serde(flatten)]
26 pub additional: serde_json::Map<String, Value>,
27}
28
29pub fn alg_to_str(alg: SignatureAlgorithm) -> &'static str {
31 match alg {
32 SignatureAlgorithm::EdDsa => "EdDSA",
33 SignatureAlgorithm::Es256 => "ES256",
34 SignatureAlgorithm::Es384 => "ES384",
35 SignatureAlgorithm::Es256k => "ES256K",
36 SignatureAlgorithm::BbsPlus => "BBS+",
37 }
38}
39
40pub fn str_to_alg(s: &str) -> baseid_core::Result<SignatureAlgorithm> {
42 match s {
43 "EdDSA" => Ok(SignatureAlgorithm::EdDsa),
44 "ES256" => Ok(SignatureAlgorithm::Es256),
45 "ES384" => Ok(SignatureAlgorithm::Es384),
46 "ES256K" => Ok(SignatureAlgorithm::Es256k),
47 _ => Err(CryptoError::UnsupportedAlgorithm.into()),
48 }
49}
50
51pub fn encode_jwt(
53 header: &JwtHeader,
54 claims: &Value,
55 signer: &dyn Signer,
56) -> baseid_core::Result<String> {
57 let header_json = serde_json::to_vec(header).map_err(SerializationError::Json)?;
58 let claims_json = serde_json::to_vec(claims).map_err(SerializationError::Json)?;
59
60 let header_b64 = URL_SAFE_NO_PAD.encode(&header_json);
61 let claims_b64 = URL_SAFE_NO_PAD.encode(&claims_json);
62
63 let signing_input = format!("{header_b64}.{claims_b64}");
64 let signature = signer.sign(signing_input.as_bytes())?;
65 let sig_b64 = URL_SAFE_NO_PAD.encode(&signature);
66
67 Ok(format!("{signing_input}.{sig_b64}"))
68}
69
70pub fn decode_jwt_unverified(jwt: &str) -> baseid_core::Result<(JwtHeader, Value)> {
75 let parts: Vec<&str> = jwt.splitn(4, '.').collect();
76 if parts.len() != 3 {
77 return Err(CryptoError::VerificationFailed.into());
78 }
79
80 let header_bytes = URL_SAFE_NO_PAD
81 .decode(parts[0])
82 .map_err(|_| CryptoError::VerificationFailed)?;
83 let claims_bytes = URL_SAFE_NO_PAD
84 .decode(parts[1])
85 .map_err(|_| CryptoError::VerificationFailed)?;
86
87 let header: JwtHeader =
88 serde_json::from_slice(&header_bytes).map_err(SerializationError::Json)?;
89 let claims: Value = serde_json::from_slice(&claims_bytes).map_err(SerializationError::Json)?;
90
91 Ok((header, claims))
92}
93
94pub fn decode_jwt(jwt: &str, verifier: &dyn Verifier) -> baseid_core::Result<(JwtHeader, Value)> {
99 let parts: Vec<&str> = jwt.splitn(4, '.').collect();
100 if parts.len() != 3 {
101 return Err(CryptoError::VerificationFailed.into());
102 }
103
104 let header_bytes = URL_SAFE_NO_PAD
106 .decode(parts[0])
107 .map_err(|_| CryptoError::VerificationFailed)?;
108 let header: JwtHeader =
109 serde_json::from_slice(&header_bytes).map_err(SerializationError::Json)?;
110
111 let expected_alg = alg_to_str(verifier.algorithm());
112 if header.alg != expected_alg {
113 return Err(CryptoError::VerificationFailed.into());
114 }
115
116 let signing_input = format!("{}.{}", parts[0], parts[1]);
117 let signature = URL_SAFE_NO_PAD
118 .decode(parts[2])
119 .map_err(|_| CryptoError::VerificationFailed)?;
120
121 let valid = verifier.verify(signing_input.as_bytes(), &signature)?;
122 if !valid {
123 return Err(CryptoError::VerificationFailed.into());
124 }
125
126 let claims_bytes = URL_SAFE_NO_PAD
127 .decode(parts[1])
128 .map_err(|_| CryptoError::VerificationFailed)?;
129 let claims: Value = serde_json::from_slice(&claims_bytes).map_err(SerializationError::Json)?;
130
131 Ok((header, claims))
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use crate::key::KeyPair;
138 use baseid_core::types::KeyType;
139
140 fn roundtrip(key_type: KeyType) {
141 let kp = KeyPair::generate(key_type).unwrap();
142 let header = JwtHeader {
143 alg: alg_to_str(kp.algorithm()).to_string(),
144 typ: Some("JWT".to_string()),
145 kid: Some("key-1".to_string()),
146 additional: serde_json::Map::new(),
147 };
148 let claims = serde_json::json!({
149 "sub": "did:key:z6Mk...",
150 "iss": "did:key:z6Mk...",
151 "exp": 9999999999u64,
152 });
153
154 let jwt = encode_jwt(&header, &claims, &kp).unwrap();
155 let (decoded_header, decoded_claims) = decode_jwt(&jwt, &kp.public).unwrap();
156
157 assert_eq!(decoded_header.alg, header.alg);
158 assert_eq!(decoded_header.typ, header.typ);
159 assert_eq!(decoded_header.kid, header.kid);
160 assert_eq!(decoded_claims, claims);
161 }
162
163 #[test]
164 fn roundtrip_ed25519() {
165 roundtrip(KeyType::Ed25519);
166 }
167
168 #[test]
169 fn roundtrip_p256() {
170 roundtrip(KeyType::P256);
171 }
172
173 #[test]
174 fn roundtrip_p384() {
175 roundtrip(KeyType::P384);
176 }
177
178 #[test]
179 fn roundtrip_secp256k1() {
180 roundtrip(KeyType::Secp256k1);
181 }
182
183 #[test]
184 fn unverified_decode() {
185 let kp = KeyPair::generate(KeyType::Ed25519).unwrap();
186 let header = JwtHeader {
187 alg: "EdDSA".to_string(),
188 typ: Some("JWT".to_string()),
189 kid: None,
190 additional: serde_json::Map::new(),
191 };
192 let claims = serde_json::json!({"sub": "test"});
193 let jwt = encode_jwt(&header, &claims, &kp).unwrap();
194
195 let (h, c) = decode_jwt_unverified(&jwt).unwrap();
196 assert_eq!(h.alg, "EdDSA");
197 assert_eq!(c["sub"], "test");
198 }
199
200 #[test]
201 fn wrong_key_rejected() {
202 let kp1 = KeyPair::generate(KeyType::Ed25519).unwrap();
203 let kp2 = KeyPair::generate(KeyType::Ed25519).unwrap();
204 let header = JwtHeader {
205 alg: "EdDSA".to_string(),
206 typ: Some("JWT".to_string()),
207 kid: None,
208 additional: serde_json::Map::new(),
209 };
210 let claims = serde_json::json!({"sub": "test"});
211 let jwt = encode_jwt(&header, &claims, &kp1).unwrap();
212
213 let result = decode_jwt(&jwt, &kp2.public);
214 assert!(result.is_err());
215 }
216
217 #[test]
218 fn tampered_claims_rejected() {
219 let kp = KeyPair::generate(KeyType::Ed25519).unwrap();
220 let header = JwtHeader {
221 alg: "EdDSA".to_string(),
222 typ: Some("JWT".to_string()),
223 kid: None,
224 additional: serde_json::Map::new(),
225 };
226 let claims = serde_json::json!({"sub": "test"});
227 let jwt = encode_jwt(&header, &claims, &kp).unwrap();
228
229 let parts: Vec<&str> = jwt.split('.').collect();
231 let fake_claims = URL_SAFE_NO_PAD.encode(b"{\"sub\":\"hacked\"}");
232 let tampered = format!("{}.{}.{}", parts[0], fake_claims, parts[2]);
233
234 let result = decode_jwt(&tampered, &kp.public);
235 assert!(result.is_err());
236 }
237
238 #[test]
239 fn malformed_jwt_rejected() {
240 let kp = KeyPair::generate(KeyType::Ed25519).unwrap();
241 assert!(decode_jwt("not-a-jwt", &kp.public).is_err());
242 assert!(decode_jwt("a.b", &kp.public).is_err());
243 assert!(decode_jwt("a.b.c.d", &kp.public).is_err());
244 assert!(decode_jwt_unverified("single").is_err());
245 }
246
247 #[test]
248 fn alg_string_roundtrip() {
249 for alg in [
250 SignatureAlgorithm::EdDsa,
251 SignatureAlgorithm::Es256,
252 SignatureAlgorithm::Es384,
253 SignatureAlgorithm::Es256k,
254 ] {
255 let s = alg_to_str(alg);
256 let back = str_to_alg(s).unwrap();
257 assert_eq!(back, alg);
258 }
259 }
260
261 #[test]
262 fn unknown_alg_rejected() {
263 assert!(str_to_alg("RS256").is_err());
264 }
265
266 #[test]
267 fn algorithm_confusion_rejected() {
268 let ed_kp = KeyPair::generate(KeyType::Ed25519).unwrap();
271 let p256_kp = KeyPair::generate(KeyType::P256).unwrap();
272
273 let header = JwtHeader {
274 alg: "EdDSA".to_string(),
275 typ: Some("JWT".to_string()),
276 kid: None,
277 additional: serde_json::Map::new(),
278 };
279 let claims = serde_json::json!({"sub": "test"});
280 let jwt = encode_jwt(&header, &claims, &ed_kp).unwrap();
281
282 let result = decode_jwt(&jwt, &p256_kp.public);
284 assert!(result.is_err(), "Algorithm confusion should be rejected");
285 }
286
287 #[test]
288 fn tampered_alg_header_rejected() {
289 let kp = KeyPair::generate(KeyType::Ed25519).unwrap();
291 let header = JwtHeader {
292 alg: "ES256".to_string(), typ: Some("JWT".to_string()),
294 kid: None,
295 additional: serde_json::Map::new(),
296 };
297 let claims = serde_json::json!({"sub": "test"});
298
299 let jwt = encode_jwt(&header, &claims, &kp).unwrap();
301
302 let result = decode_jwt(&jwt, &kp.public);
304 assert!(result.is_err(), "Tampered alg header should be rejected");
305 }
306
307 mod interop {
310 use super::*;
311 use base64::engine::general_purpose::URL_SAFE_NO_PAD;
312
313 #[test]
315 fn baseid_sign_jsonwebtoken_verify_ed25519() {
316 let kp = KeyPair::generate(KeyType::Ed25519).unwrap();
317
318 let header = JwtHeader {
319 alg: "EdDSA".to_string(),
320 typ: Some("JWT".to_string()),
321 kid: None,
322 additional: serde_json::Map::new(),
323 };
324 let claims = serde_json::json!({
325 "sub": "user123",
326 "iss": "baseid",
327 "exp": 9999999999u64,
328 });
329 let jwt_str = encode_jwt(&header, &claims, &kp).unwrap();
330
331 let x_b64 = URL_SAFE_NO_PAD.encode(&kp.public.bytes);
333 let dk = jsonwebtoken::DecodingKey::from_ed_components(&x_b64).unwrap();
334
335 let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::EdDSA);
336 validation.set_required_spec_claims::<String>(&[]);
337 validation.validate_exp = false;
338
339 let token_data: jsonwebtoken::TokenData<serde_json::Value> =
340 jsonwebtoken::decode(&jwt_str, &dk, &validation).unwrap();
341
342 assert_eq!(token_data.claims["sub"], "user123");
343 assert_eq!(token_data.claims["iss"], "baseid");
344 }
345
346 #[test]
348 fn jsonwebtoken_sign_baseid_verify_ed25519() {
349 let kp = KeyPair::generate(KeyType::Ed25519).unwrap();
350
351 let signing_key =
353 ed25519_dalek::SigningKey::from_bytes(kp.secret_bytes().try_into().unwrap());
354 let pkcs8_der = build_ed25519_pkcs8_der(&signing_key);
355 let ek = jsonwebtoken::EncodingKey::from_ed_der(&pkcs8_der);
356
357 let jw_header = jsonwebtoken::Header::new(jsonwebtoken::Algorithm::EdDSA);
358 let claims = serde_json::json!({
359 "sub": "from_jsonwebtoken",
360 "iss": "external",
361 "exp": 9999999999u64,
362 });
363 let jwt_str = jsonwebtoken::encode(&jw_header, &claims, &ek).unwrap();
364
365 let (decoded_header, decoded_claims) = decode_jwt(&jwt_str, &kp.public).unwrap();
367 assert_eq!(decoded_header.alg, "EdDSA");
368 assert_eq!(decoded_claims["sub"], "from_jsonwebtoken");
369 assert_eq!(decoded_claims["iss"], "external");
370 }
371
372 #[test]
374 fn baseid_sign_jsonwebtoken_verify_p256() {
375 let kp = KeyPair::generate(KeyType::P256).unwrap();
376
377 let header = JwtHeader {
378 alg: "ES256".to_string(),
379 typ: Some("JWT".to_string()),
380 kid: None,
381 additional: serde_json::Map::new(),
382 };
383 let claims = serde_json::json!({"sub": "p256_test", "exp": 9999999999u64});
384 let jwt_str = encode_jwt(&header, &claims, &kp).unwrap();
385
386 let (x, y) = decompress_p256(&kp.public.bytes);
388 let x_b64 = URL_SAFE_NO_PAD.encode(&x);
389 let y_b64 = URL_SAFE_NO_PAD.encode(&y);
390
391 let dk = jsonwebtoken::DecodingKey::from_ec_components(&x_b64, &y_b64).unwrap();
392 let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::ES256);
393 validation.set_required_spec_claims::<String>(&[]);
394 validation.validate_exp = false;
395
396 let token_data: jsonwebtoken::TokenData<serde_json::Value> =
397 jsonwebtoken::decode(&jwt_str, &dk, &validation).unwrap();
398 assert_eq!(token_data.claims["sub"], "p256_test");
399 }
400
401 #[test]
403 fn baseid_sign_jsonwebtoken_verify_p384() {
404 let kp = KeyPair::generate(KeyType::P384).unwrap();
405
406 let header = JwtHeader {
407 alg: "ES384".to_string(),
408 typ: Some("JWT".to_string()),
409 kid: None,
410 additional: serde_json::Map::new(),
411 };
412 let claims = serde_json::json!({"sub": "p384_test", "exp": 9999999999u64});
413 let jwt_str = encode_jwt(&header, &claims, &kp).unwrap();
414
415 let (x, y) = decompress_p384(&kp.public.bytes);
416 let x_b64 = URL_SAFE_NO_PAD.encode(&x);
417 let y_b64 = URL_SAFE_NO_PAD.encode(&y);
418
419 let dk = jsonwebtoken::DecodingKey::from_ec_components(&x_b64, &y_b64).unwrap();
420 let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::ES384);
421 validation.set_required_spec_claims::<String>(&[]);
422 validation.validate_exp = false;
423
424 let token_data: jsonwebtoken::TokenData<serde_json::Value> =
425 jsonwebtoken::decode(&jwt_str, &dk, &validation).unwrap();
426 assert_eq!(token_data.claims["sub"], "p384_test");
427 }
428
429 fn build_ed25519_pkcs8_der(signing_key: &ed25519_dalek::SigningKey) -> Vec<u8> {
432 let seed = signing_key.to_bytes();
436 let mut der = Vec::new();
437 der.push(0x30);
439 der.push(0x2e); der.extend_from_slice(&[0x02, 0x01, 0x00]);
442 der.extend_from_slice(&[0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70]);
444 der.push(0x04); der.push(0x22); der.push(0x04); der.push(0x20); der.extend_from_slice(&seed);
450 der
451 }
452
453 fn decompress_p256(compressed: &[u8]) -> (Vec<u8>, Vec<u8>) {
455 use p256::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
456 let point = p256::EncodedPoint::from_bytes(compressed).unwrap();
457 let affine = p256::AffinePoint::from_encoded_point(&point).unwrap();
458 let uncompressed = affine.to_encoded_point(false);
459 (
460 uncompressed.x().unwrap().to_vec(),
461 uncompressed.y().unwrap().to_vec(),
462 )
463 }
464
465 fn decompress_p384(compressed: &[u8]) -> (Vec<u8>, Vec<u8>) {
467 use p384::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
468 let point = p384::EncodedPoint::from_bytes(compressed).unwrap();
469 let affine = p384::AffinePoint::from_encoded_point(&point).unwrap();
470 let uncompressed = affine.to_encoded_point(false);
471 (
472 uncompressed.x().unwrap().to_vec(),
473 uncompressed.y().unwrap().to_vec(),
474 )
475 }
476 }
477}