1#![cfg_attr(not(feature = "std"), no_std)]
99
100extern crate alloc;
101
102use alloc::{
103 string::{String, ToString},
104 vec::Vec,
105};
106use core::time::Duration;
107
108use serde::{Deserialize, Serialize, de::DeserializeOwned};
109use small_collections::SmallString;
110
111mod jwk;
112mod jwt_crypto;
113
114pub use jwk::*;
115pub use jwt_crypto::*;
116
117#[cfg(feature = "std")]
118pub static SYSTEM_CLOCK: SystemClock = SystemClock;
119
120#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
121pub struct Header {
122 pub typ: TokenType,
125
126 pub alg: Algorithm,
128
129 #[serde(skip_serializing_if = "Option::is_none")]
132 pub cty: Option<SmallString<3>>,
133
134 #[serde(skip_serializing_if = "Option::is_none")]
137 pub jku: Option<String>,
138
139 #[serde(skip_serializing_if = "Option::is_none")]
147 pub kid: Option<SmallString<36>>, #[serde(skip_serializing_if = "Option::is_none")]
152 pub x5u: Option<String>,
153
154 #[serde(skip_serializing_if = "Option::is_none")]
157 pub x5c: Option<Vec<String>>,
158
159 #[serde(skip_serializing_if = "Option::is_none")]
162 pub x5t: Option<SmallString<27>>, #[serde(skip_serializing_if = "Option::is_none")]
167 #[serde(rename = "x5t#S256")]
168 pub x5t_s256: Option<SmallString<43>>, }
170
171impl Default for Header {
172 fn default() -> Self {
173 Self {
174 typ: TokenType::JWT,
175 alg: Algorithm::EdDSA,
176 cty: None,
177 jku: None,
178 kid: None,
179 x5u: None,
180 x5c: None,
181 x5t: None,
182 x5t_s256: None,
183 }
184 }
185}
186
187#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
189pub struct RegisteredClaims {
190 #[serde(skip_serializing_if = "Option::is_none")]
193 pub iss: Option<SmallString<20>>,
194
195 #[serde(skip_serializing_if = "Option::is_none")]
198 pub sub: Option<SmallString<36>>, #[serde(skip_serializing_if = "Option::is_none")]
203 pub aud: Option<SmallString<20>>,
204
205 #[serde(skip_serializing_if = "Option::is_none")]
208 pub exp: Option<u64>,
209
210 #[serde(skip_serializing_if = "Option::is_none")]
213 pub nbf: Option<u64>,
214
215 #[serde(skip_serializing_if = "Option::is_none")]
218 pub iat: Option<u64>,
219
220 #[serde(skip_serializing_if = "Option::is_none")]
223 pub jti: Option<SmallString<36>>,
224}
225
226#[derive(Debug, Default, PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize)]
227pub enum TokenType {
228 #[default]
229 JWT,
230}
231
232#[derive(Copy, Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
234pub enum Algorithm {
235 HS256,
237
238 HS384,
240
241 HS512,
243
244 BLAKE3,
246
247 EdDSA,
249
250 ES256,
252
253 ES384,
255
256 ES512,
258
259 MlDsa44,
261
262 MlDsa65,
264
265 RS256,
267
268 RS384,
270
271 RS512,
273
274 PS256,
276
277 PS384,
279
280 PS512,
282}
283
284impl Algorithm {
285 #[inline]
290 pub(crate) fn signature_max_size(&self) -> usize {
291 match self {
292 Algorithm::BLAKE3 => 32,
293 Algorithm::HS256 => 32,
294 Algorithm::HS384 => 48,
295 Algorithm::HS512 => 64,
296 Algorithm::EdDSA => 64,
297 Algorithm::ES256 => 64,
298 Algorithm::ES384 => 96,
299 Algorithm::ES512 => 132,
300 Algorithm::RS256 => 512,
301 Algorithm::RS384 => 512,
302 Algorithm::RS512 => 512,
303 Algorithm::PS256 => 512,
304 Algorithm::PS384 => 512,
305 Algorithm::PS512 => 512,
306 Algorithm::MlDsa44 => 2420,
307 Algorithm::MlDsa65 => 3309,
308 }
310 }
311}
312
313impl core::str::FromStr for Algorithm {
314 type Err = Error;
315
316 fn from_str(s: &str) -> Result<Self, Self::Err> {
317 match s {
318 "BLAKE3" => Ok(Algorithm::BLAKE3),
319 "HS256" => Ok(Algorithm::HS256),
320 "HS384" => Ok(Algorithm::HS384),
321 "HS512" => Ok(Algorithm::HS512),
322 "ES256" => Ok(Algorithm::ES256),
323 "ES384" => Ok(Algorithm::ES384),
324 "ES512" => Ok(Algorithm::ES512),
325 "EdDSA" => Ok(Algorithm::EdDSA),
326 "ML-DSA-44" => Ok(Algorithm::MlDsa44),
327 "ML-DSA-65" => Ok(Algorithm::MlDsa65),
328 "RS256" => Ok(Algorithm::RS256),
329 "RS384" => Ok(Algorithm::RS384),
330 "RS512" => Ok(Algorithm::RS512),
331 "PS256" => Ok(Algorithm::PS256),
332 "PS384" => Ok(Algorithm::PS384),
333 "PS512" => Ok(Algorithm::PS512),
334 _ => Err(Error::UnknownAlgorithm(s.to_string())),
335 }
336 }
337}
338
339impl core::fmt::Display for Algorithm {
340 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
341 write!(f, "{self:?}")
342 }
343}
344
345#[derive(Debug)]
346pub enum Error {
347 UnknownAlgorithm(String),
348 InvalidCurve,
349 InvalidTokenType(String),
350 Json(serde_json::Error),
351 InvalidToken,
352 InvalidSignature,
353 InvalidKey,
354 InvalidEllipticCurve(String),
355 InvalidJwk { kid: String, err: String },
356 Unspecified(String),
357 ClockIsMissing,
358}
359
360impl core::fmt::Display for Error {
361 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
362 match self {
363 Error::UnknownAlgorithm(algorithm) => write!(f, "unknown algorithm: {algorithm}"),
364 Error::InvalidCurve => f.write_str("invalid curve"),
365 Error::InvalidTokenType(token_type) => write!(f, "invalid token type: {token_type}"),
366 Error::Json(err) => write!(f, "error serializing JWT to JSON: {err}"),
367 Error::InvalidToken => f.write_str("JWT is not valid"),
368 Error::InvalidSignature => f.write_str("signature is not valid"),
369 Error::InvalidKey => f.write_str("key is not valid"),
370 Error::InvalidEllipticCurve(curve) => write!(f, "invalid elliptic curve: {curve}"),
371 Error::InvalidJwk {
372 kid,
373 err,
374 } => write!(f, "{kid} is not a valid JWK: {err}"),
375 Error::Unspecified(err) => f.write_str(&err),
376 Error::ClockIsMissing => f.write_str("a clock is needed for exp or nbf verification"),
377 }
378 }
379}
380
381impl From<serde_json::Error> for Error {
382 fn from(err: serde_json::Error) -> Self {
383 Self::Json(err)
384 }
385}
386
387#[cfg(feature = "std")]
388impl std::error::Error for Error {}
389
390pub trait Clock: Send + Sync {
396 fn now(&self) -> u64;
397}
398
399#[cfg(feature = "std")]
403pub struct SystemClock;
404
405#[cfg(feature = "std")]
406impl Clock for SystemClock {
407 #[inline]
408 fn now(&self) -> u64 {
409 std::time::SystemTime::now()
410 .duration_since(std::time::UNIX_EPOCH)
411 .unwrap_or_default()
412 .as_secs()
413 }
414}
415
416pub struct VerifyOptions<'a> {
417 pub allowed_time_drift: Duration,
421 pub nbf: bool,
423 pub exp: bool,
425 pub aud: Option<&'a [&'a str]>,
427 pub iss: Option<&'a [&'a str]>,
429 pub clock: Option<&'a dyn Clock>,
431}
432
433#[cfg(feature = "std")]
434impl Default for VerifyOptions<'_> {
435 fn default() -> Self {
436 Self {
437 allowed_time_drift: core::time::Duration::from_secs(30),
438 nbf: true,
439 exp: true,
440 aud: None,
441 iss: None,
442 clock: Some(&SYSTEM_CLOCK),
443 }
444 }
445}
446
447pub fn sign<C: Serialize>(key: &dyn Signer, header: &Header, claims: &C) -> Result<String, Error> {
448 let signing_algorithm = key.algorithm();
449 if signing_algorithm != header.alg {
450 return Err(Error::InvalidKey);
451 }
452
453 let header_base64 = base64::encode(serde_json::to_string(header)?.as_bytes(), base64::Alphabet::UrlNoPadding);
454 let claims_base64 = base64::encode(serde_json::to_string(claims)?.as_bytes(), base64::Alphabet::UrlNoPadding);
455
456 let mut jwt = String::with_capacity(
457 header_base64.len()
458 + claims_base64.len()
459 + base64::encoded_length(signing_algorithm.signature_max_size(), false)
460 .expect("error getting base64 encoding length")
461 + 2,
462 );
463 jwt.push_str(&header_base64);
464 jwt.push('.');
465 jwt.push_str(&claims_base64);
466
467 let signature = key.sign(jwt.as_bytes())?;
468 jwt.push('.');
469 jwt.push_str(&base64::encode(signature.as_ref(), base64::Alphabet::UrlNoPadding));
470
471 return Ok(jwt);
472}
473
474pub fn parse_header(token: &str) -> Result<Header, Error> {
475 let mut parts = token.split('.');
476 let header_base64 = parts.next().ok_or(Error::InvalidToken)?;
477 if parts.count() != 2 {
478 return Err(Error::InvalidToken);
479 }
480
481 let header_json = base64::decode(header_base64, base64::Alphabet::UrlNoPadding).map_err(|_| Error::InvalidToken)?;
482 let header: Header = serde_json::from_slice(&header_json).map_err(|_| Error::InvalidToken)?;
483
484 return Ok(header);
485}
486
487pub fn parse_and_verify<C: DeserializeOwned>(
488 key: &dyn Verifier,
489 header: &Header,
490 token: &str,
491 verify_options: &VerifyOptions,
492) -> Result<C, Error> {
493 if (verify_options.exp || verify_options.nbf) && verify_options.clock.is_none() {
494 return Err(Error::ClockIsMissing);
495 }
496
497 if header.alg != key.algorithm() {
498 return Err(Error::InvalidToken);
499 }
500
501 let mut parts = token.split('.');
502 let header_base64 = parts.next().ok_or(Error::InvalidToken)?;
503 let claims_base64 = parts.next().ok_or(Error::InvalidToken)?;
504 let signature_base64 = parts.next().ok_or(Error::InvalidToken)?;
505 if parts.next().is_some() {
506 return Err(Error::InvalidToken);
507 }
508
509 let mut signature_buffer = [0u8; SIGNATURE_MAX_SIZE];
510 let signature_size = base64::decode_into(
511 &mut signature_buffer,
512 signature_base64.as_bytes(),
513 base64::Alphabet::UrlNoPadding,
514 )
515 .map_err(|_| Error::InvalidSignature)?;
516
517 let signed_message = &token[..header_base64.len() + 1 + claims_base64.len()].as_bytes();
518 key.verify(signed_message, &signature_buffer[..signature_size])
519 .map_err(|_| Error::InvalidSignature)?;
520
521 let claims_json =
522 base64::decode(&claims_base64, base64::Alphabet::UrlNoPadding).map_err(|_| Error::InvalidToken)?;
523
524 let claims =
525 if verify_options.exp || verify_options.nbf || verify_options.aud.is_some() || verify_options.iss.is_some() {
526 let claims_json_value: serde_json::Value =
527 serde_json::from_slice(&claims_json).map_err(|_| Error::InvalidToken)?;
528
529 match &claims_json_value {
530 serde_json::Value::Object(claims_object) => {
531 if verify_options.exp {
532 match claims_object.get("exp") {
533 None => return Err(Error::InvalidToken),
534 Some(exp_value) => {
535 if let Some(exp) = exp_value.as_u64() {
536 let now = verify_options.clock.unwrap().now();
537 if exp < (now - verify_options.allowed_time_drift.as_secs()) {
538 return Err(Error::InvalidToken);
539 }
540 } else {
541 return Err(Error::InvalidToken);
542 }
543 }
544 }
545 }
546
547 if verify_options.nbf {
548 match claims_object.get("nbf") {
549 None => return Err(Error::InvalidToken),
550 Some(nbf_value) => {
551 if let Some(nbf) = nbf_value.as_u64() {
552 let now = verify_options.clock.unwrap().now();
553 if nbf > (now + verify_options.allowed_time_drift.as_secs()) {
554 return Err(Error::InvalidToken);
555 }
556 } else {
557 return Err(Error::InvalidToken);
558 }
559 }
560 }
561 }
562
563 if let Some(expected_aud) = verify_options.aud {
564 match claims_object.get("aud") {
565 None => return Err(Error::InvalidToken),
566 Some(aud_value) => {
567 if let Some(aud) = aud_value.as_str() {
568 if !expected_aud.contains(&aud) {
569 return Err(Error::InvalidToken);
570 }
571 } else {
572 return Err(Error::InvalidToken);
573 }
574 }
575 }
576 }
577
578 if let Some(expected_iss) = verify_options.iss {
579 match claims_object.get("iss") {
580 None => return Err(Error::InvalidToken),
581 Some(iss_value) => {
582 if let Some(iss) = iss_value.as_str() {
583 if !expected_iss.contains(&iss) {
584 return Err(Error::InvalidToken);
585 }
586 } else {
587 return Err(Error::InvalidToken);
588 }
589 }
590 }
591 }
592 }
593 _ => return Err(Error::InvalidToken),
594 };
595
596 serde_json::from_value(claims_json_value).map_err(|_| Error::InvalidToken)?
597 } else {
598 serde_json::from_slice(&claims_json).map_err(|_| Error::InvalidToken)?
599 };
600
601 return Ok(claims);
602}