1use crate::errors::{JwtError, JwtOperation};
20use crate::jwt::authority::JwtAuthority;
21use crate::{Codec, Error, Result};
22
23use std::collections::HashMap;
24use std::collections::HashSet;
25use std::marker::PhantomData;
26use std::path::{Path, PathBuf};
27use std::sync::RwLock;
28
29use chrono::Utc;
30use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation, decode_header};
31use p384::elliptic_curve::rand_core::OsRng;
32use p384::pkcs8::{EncodePrivateKey, EncodePublicKey, LineEnding};
33use serde::{Deserialize, Serialize, de::DeserializeOwned};
34use serde_with::skip_serializing_none;
35use uuid::Uuid;
36
37pub mod authority;
38pub mod jwks;
39pub mod remote_verifier;
40pub mod validation_result;
41pub mod validation_service;
42
43fn validation_with_es384_only() -> Validation {
44 let mut validation = Validation::new(Algorithm::ES384);
45 validation.algorithms = vec![Algorithm::ES384];
46 validation
47}
48
49fn canonical_es384_header_with_kid(kid: &str) -> Header {
50 let mut header = Header::new(Algorithm::ES384);
51 header.typ = Some("JWT".to_string());
52 header.kid = Some(kid.to_string());
53 header
54}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
58pub struct Es384KeyPairPaths {
59 private_key_path: PathBuf,
60 public_key_path: PathBuf,
61}
62
63impl Es384KeyPairPaths {
64 pub fn new(private_key_path: impl Into<PathBuf>, public_key_path: impl Into<PathBuf>) -> Self {
66 Self {
67 private_key_path: private_key_path.into(),
68 public_key_path: public_key_path.into(),
69 }
70 }
71
72 pub fn private_key_path(&self) -> &Path {
74 &self.private_key_path
75 }
76
77 pub fn public_key_path(&self) -> &Path {
79 &self.public_key_path
80 }
81
82 pub fn both_exist(&self) -> bool {
84 self.private_key_path.is_file() && self.public_key_path.is_file()
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct Es384KeyPair {
91 paths: Es384KeyPairPaths,
92 private_key_pem: Vec<u8>,
93 public_key_pem: Vec<u8>,
94}
95
96impl Es384KeyPair {
97 pub fn new(
99 paths: Es384KeyPairPaths,
100 private_key_pem: impl Into<Vec<u8>>,
101 public_key_pem: impl Into<Vec<u8>>,
102 ) -> Self {
103 Self {
104 paths,
105 private_key_pem: private_key_pem.into(),
106 public_key_pem: public_key_pem.into(),
107 }
108 }
109
110 pub fn paths(&self) -> &Es384KeyPairPaths {
112 &self.paths
113 }
114
115 pub fn private_key_path(&self) -> &Path {
117 self.paths.private_key_path()
118 }
119
120 pub fn public_key_path(&self) -> &Path {
122 self.paths.public_key_path()
123 }
124
125 pub fn private_key_pem(&self) -> &[u8] {
127 &self.private_key_pem
128 }
129
130 pub fn public_key_pem(&self) -> &[u8] {
132 &self.public_key_pem
133 }
134
135 pub fn to_jwt_options(&self) -> Result<JsonWebTokenOptions> {
137 JsonWebTokenOptions::from_es384_pem(&self.private_key_pem, &self.public_key_pem)
138 }
139
140 pub fn to_codec<P>(&self) -> Result<JsonWebToken<P>> {
142 Ok(JsonWebToken::new_with_options(self.to_jwt_options()?))
143 }
144
145 pub fn to_authority<P>(&self) -> Result<JwtAuthority<P>>
147 where
148 P: Serialize + DeserializeOwned + Clone,
149 {
150 JwtAuthority::from_es384_pem(&self.private_key_pem, &self.public_key_pem)
151 }
152}
153
154#[derive(Debug, Clone, PartialEq, Eq)]
156pub struct Es384KeyPairLoader {
157 paths: Es384KeyPairPaths,
158}
159
160impl Es384KeyPairLoader {
161 pub fn new(private_key_path: impl Into<PathBuf>, public_key_path: impl Into<PathBuf>) -> Self {
163 Self {
164 paths: Es384KeyPairPaths::new(private_key_path, public_key_path),
165 }
166 }
167
168 pub fn paths(&self) -> &Es384KeyPairPaths {
170 &self.paths
171 }
172
173 pub async fn initialize_if_required(&self) -> Result<Es384KeyPair> {
184 let private_exists = self.paths.private_key_path.is_file();
185 let public_exists = self.paths.public_key_path.is_file();
186
187 match (private_exists, public_exists) {
188 (true, true) => self.load().await,
189 (false, false) => {
190 self.create_parent_directories().await?;
191 let (private_key_pem, public_key_pem) = generate_es384_key_pair_pem()?;
192 tokio::fs::write(&self.paths.private_key_path, &private_key_pem)
193 .await
194 .map_err(|error| {
195 Error::Jwt(JwtError::processing(
196 JwtOperation::Encode,
197 format!(
198 "failed to write ES384 private key `{}`: {error}",
199 self.paths.private_key_path.display()
200 ),
201 ))
202 })?;
203 tokio::fs::write(&self.paths.public_key_path, &public_key_pem)
204 .await
205 .map_err(|error| {
206 Error::Jwt(JwtError::processing(
207 JwtOperation::Encode,
208 format!(
209 "failed to write ES384 public key `{}`: {error}",
210 self.paths.public_key_path.display()
211 ),
212 ))
213 })?;
214 self.validate_loaded_pair(Es384KeyPair::new(
215 self.paths.clone(),
216 private_key_pem,
217 public_key_pem,
218 ))
219 }
220 _ => Err(Error::Jwt(JwtError::processing(
221 JwtOperation::Encode,
222 format!(
223 "ES384 key initialization requires both key files to exist or neither to exist; private=`{}`, public=`{}`",
224 self.paths.private_key_path.display(),
225 self.paths.public_key_path.display()
226 ),
227 ))),
228 }
229 }
230
231 pub async fn load(&self) -> Result<Es384KeyPair> {
238 let private_key_pem = tokio::fs::read(&self.paths.private_key_path)
239 .await
240 .map_err(|error| {
241 Error::Jwt(JwtError::processing(
242 JwtOperation::Decode,
243 format!(
244 "failed to read ES384 private key `{}`: {error}",
245 self.paths.private_key_path.display()
246 ),
247 ))
248 })?;
249 let public_key_pem =
250 tokio::fs::read(&self.paths.public_key_path)
251 .await
252 .map_err(|error| {
253 Error::Jwt(JwtError::processing(
254 JwtOperation::Decode,
255 format!(
256 "failed to read ES384 public key `{}`: {error}",
257 self.paths.public_key_path.display()
258 ),
259 ))
260 })?;
261
262 self.validate_loaded_pair(Es384KeyPair::new(
263 self.paths.clone(),
264 private_key_pem,
265 public_key_pem,
266 ))
267 }
268
269 async fn create_parent_directories(&self) -> Result<()> {
270 if let Some(parent) = self.paths.private_key_path.parent()
271 && !parent.as_os_str().is_empty()
272 {
273 tokio::fs::create_dir_all(parent).await.map_err(|error| {
274 Error::Jwt(JwtError::processing(
275 JwtOperation::Encode,
276 format!(
277 "failed to create private key directory `{}`: {error}",
278 parent.display()
279 ),
280 ))
281 })?;
282 }
283
284 if let Some(parent) = self.paths.public_key_path.parent()
285 && !parent.as_os_str().is_empty()
286 {
287 tokio::fs::create_dir_all(parent).await.map_err(|error| {
288 Error::Jwt(JwtError::processing(
289 JwtOperation::Encode,
290 format!(
291 "failed to create public key directory `{}`: {error}",
292 parent.display()
293 ),
294 ))
295 })?;
296 }
297
298 Ok(())
299 }
300
301 fn validate_loaded_pair(&self, key_pair: Es384KeyPair) -> Result<Es384KeyPair> {
302 key_pair.to_jwt_options()?;
303 Ok(key_pair)
304 }
305}
306
307fn generate_es384_key_pair_pem() -> Result<(Vec<u8>, Vec<u8>)> {
308 let signing_key = p384::ecdsa::SigningKey::random(&mut OsRng);
309 let verifying_key = signing_key.verifying_key();
310
311 let private_key_pem = signing_key
312 .to_pkcs8_pem(LineEnding::LF)
313 .map_err(|error| {
314 Error::Jwt(JwtError::processing(
315 JwtOperation::Encode,
316 format!("failed to encode ES384 private key as PKCS#8 PEM: {error}"),
317 ))
318 })?
319 .to_string()
320 .into_bytes();
321 let public_key_pem = verifying_key
322 .to_public_key_pem(LineEnding::LF)
323 .map_err(|error| {
324 Error::Jwt(JwtError::processing(
325 JwtOperation::Encode,
326 format!("failed to encode ES384 public key as PEM: {error}"),
327 ))
328 })?
329 .into_bytes();
330
331 Ok((private_key_pem, public_key_pem))
332}
333
334#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
339#[skip_serializing_none]
340pub struct RegisteredClaims {
341 #[serde(rename = "iss")]
343 pub issuer: String,
344 #[serde(rename = "sub")]
346 pub subject: Option<String>,
347 #[serde(rename = "aud")]
349 pub audience: Option<HashSet<String>>,
350 #[serde(rename = "exp")]
352 pub expiration_time: u64,
353 #[serde(rename = "nbf")]
355 pub not_before_time: Option<u64>,
356 #[serde(rename = "iat")]
358 pub issued_at_time: u64,
359 #[serde(rename = "jti")]
361 pub jwt_id: Option<String>,
362 #[serde(rename = "sid")]
364 pub session_id: Option<String>,
365}
366
367impl RegisteredClaims {
368 pub fn new(issuer: &str, expiration_time: u64) -> Self {
370 let issued_at_time = u64::try_from(Utc::now().timestamp()).unwrap_or(0);
374 Self {
375 issuer: issuer.to_string(),
376 subject: None,
377 audience: None,
378 expiration_time,
379 not_before_time: None,
380 issued_at_time,
381 jwt_id: Some(Uuid::now_v7().to_string()),
382 session_id: None,
383 }
384 }
385
386 #[must_use]
388 pub fn with_session_id(mut self, session_id: impl Into<String>) -> Self {
389 self.session_id = Some(session_id.into());
390 self
391 }
392}
393
394#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
398pub struct JwtClaims<CustomClaims> {
399 #[serde(flatten)]
401 pub registered_claims: RegisteredClaims,
402 #[serde(flatten)]
404 pub custom_claims: CustomClaims,
405}
406
407impl<CustomClaims> JwtClaims<CustomClaims> {
408 pub fn new(custom_claims: CustomClaims, registered_claims: RegisteredClaims) -> Self {
410 Self {
411 custom_claims,
412 registered_claims,
413 }
414 }
415
416 pub fn has_issuer(&self, issuer: &str) -> bool {
418 self.registered_claims.issuer == issuer
419 }
420}
421
422#[derive(Debug, Clone)]
427pub struct JsonWebTokenOptions {
428 encoding_key: Option<EncodingKey>,
430 key_id: String,
432 decoding_keys_by_kid: HashMap<String, DecodingKey>,
434 fallback_decoding_key: Option<DecodingKey>,
436 header: Header,
438 validation: Validation,
440}
441
442const DEV_ES384_PRIVATE_KEY_PEM: &[u8] = br#"-----BEGIN PRIVATE KEY-----
443MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCFT7MfRqWZfNgVX/cH
444bxFTlPkBeCKqjsLkZXD/J3ZYHV1EtQksdrKtOzTr2hMs6pmhZANiAASyND9eQ5Qk
4457ZteSEPMpExbVJenRWwyobExJMb62mmp3eA7Fszy8uBbLj8HRB16y3QbLcTxCBoo
446ldBXfNFzM133OuTV2bBWXq5h34l+A0h4gU/odZ678LfAgnrRYMG4ZjU=
447-----END PRIVATE KEY-----
448"#;
449
450const DEV_ES384_PUBLIC_KEY_PEM: &[u8] = br#"-----BEGIN PUBLIC KEY-----
451MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEsjQ/XkOUJO2bXkhDzKRMW1SXp0VsMqGx
452MSTG+tppqd3gOxbM8vLgWy4/B0Qdest0Gy3E8QgaKJXQV3zRczNd9zrk1dmwVl6u
453Yd+JfgNIeIFP6HWeu/C3wIJ60WDBuGY1
454-----END PUBLIC KEY-----
455"#;
456
457impl Default for JsonWebTokenOptions {
458 fn default() -> Self {
464 match Self::from_es384_pem(DEV_ES384_PRIVATE_KEY_PEM, DEV_ES384_PUBLIC_KEY_PEM) {
465 Ok(options) => options,
466 Err(error) => panic!("failed to initialize default ES384 JWT options: {error}"),
467 }
468 }
469}
470
471impl JsonWebTokenOptions {
472 pub fn from_es384_pem(private_key_pem: &[u8], public_key_pem: &[u8]) -> Result<Self> {
478 let encoding_key = EncodingKey::from_ec_pem(private_key_pem).map_err(|error| {
479 Error::Jwt(JwtError::processing(
480 JwtOperation::Encode,
481 format!("failed to parse ES384 private key: {error}"),
482 ))
483 })?;
484 let decoding_key = DecodingKey::from_ec_pem(public_key_pem).map_err(|error| {
485 Error::Jwt(JwtError::processing(
486 JwtOperation::Decode,
487 format!("failed to parse ES384 public key: {error}"),
488 ))
489 })?;
490 let key_id = jwks::es384_kid_from_public_key_pem(public_key_pem)?;
491 let header = canonical_es384_header_with_kid(&key_id);
492 let validation = validation_with_es384_only();
493 let mut decoding_keys_by_kid = HashMap::new();
494 decoding_keys_by_kid.insert(key_id.clone(), decoding_key.clone());
495
496 Ok(Self {
497 encoding_key: Some(encoding_key),
498 key_id,
499 decoding_keys_by_kid,
500 fallback_decoding_key: Some(decoding_key),
501 header,
502 validation,
503 })
504 }
505
506 pub fn for_es384_verification_only(public_key_pem: &[u8]) -> Result<Self> {
515 let decoding_key = DecodingKey::from_ec_pem(public_key_pem).map_err(|error| {
516 Error::Jwt(JwtError::processing(
517 JwtOperation::Decode,
518 format!("failed to parse ES384 public key: {error}"),
519 ))
520 })?;
521 let key_id = jwks::es384_kid_from_public_key_pem(public_key_pem)?;
522 let header = canonical_es384_header_with_kid(&key_id);
523 let validation = validation_with_es384_only();
524 let mut decoding_keys_by_kid = HashMap::new();
525 decoding_keys_by_kid.insert(key_id.clone(), decoding_key.clone());
526
527 Ok(Self {
528 encoding_key: None,
529 key_id,
530 decoding_keys_by_kid,
531 fallback_decoding_key: Some(decoding_key),
532 header,
533 validation,
534 })
535 }
536
537 pub fn for_es384_jwks_keys(keys: &[jwks::EcP384Jwk]) -> Result<Self> {
547 if keys.is_empty() {
548 return Err(Error::Jwt(JwtError::processing(
549 JwtOperation::Validate,
550 "JWKS key set is empty",
551 )));
552 }
553
554 let mut decoding_keys_by_kid = HashMap::new();
555 for key in keys {
556 let decoding_key = key.to_decoding_key()?;
557 decoding_keys_by_kid.insert(key.kid.clone(), decoding_key);
558 }
559
560 let key_id = keys[0].kid.clone();
561 let header = canonical_es384_header_with_kid(&key_id);
562 let validation = validation_with_es384_only();
563
564 Ok(Self {
565 encoding_key: None,
566 key_id,
567 decoding_keys_by_kid,
568 fallback_decoding_key: None,
569 header,
570 validation,
571 })
572 }
573
574 pub fn key_id(&self) -> &str {
576 &self.key_id
577 }
578
579 pub fn verification_key_count(&self) -> usize {
581 self.decoding_keys_by_kid.len()
582 }
583
584 pub fn allows_missing_kid_fallback(&self) -> bool {
587 self.fallback_decoding_key.is_some()
588 }
589
590 pub fn with_key_id(mut self, key_id: impl Into<String>) -> Self {
595 let key_id = key_id.into();
596 self.key_id = key_id.clone();
597 self.header = canonical_es384_header_with_kid(&key_id);
598 self
599 }
600
601 pub fn with_verification_keys(
606 mut self,
607 keys: HashMap<String, DecodingKey>,
608 allow_missing_kid_fallback: bool,
609 ) -> Self {
610 let fallback = if allow_missing_kid_fallback {
611 keys.values().next().cloned()
612 } else {
613 None
614 };
615 self.decoding_keys_by_kid = keys;
616 self.fallback_decoding_key = fallback;
617 self
618 }
619
620 pub fn with_added_verification_key(mut self, kid: impl Into<String>, key: DecodingKey) -> Self {
622 self.decoding_keys_by_kid.insert(kid.into(), key);
623 self
624 }
625
626 pub fn with_validation(self, validation: Validation) -> Self {
628 let mut validation = validation;
629 validation.algorithms = vec![Algorithm::ES384];
630
631 Self { validation, ..self }
632 }
633}
634
635#[derive(Clone)]
647pub struct JsonWebToken<P> {
648 enc_key: Option<EncodingKey>,
649 key_id: String,
650 verification_state: std::sync::Arc<RwLock<VerificationState>>,
651 header: Header,
652 validation: Validation,
653 phantom_payload: PhantomData<P>,
654}
655
656#[derive(Clone)]
657struct VerificationState {
658 dec_keys_by_kid: HashMap<String, DecodingKey>,
659 fallback_dec_key: Option<DecodingKey>,
660}
661
662impl<P> JsonWebToken<P> {
663 pub fn new_with_options(options: JsonWebTokenOptions) -> Self {
667 let JsonWebTokenOptions {
668 encoding_key,
669 key_id,
670 decoding_keys_by_kid,
671 fallback_decoding_key,
672 header,
673 validation,
674 } = options;
675
676 Self {
677 enc_key: encoding_key,
678 key_id,
679 verification_state: std::sync::Arc::new(RwLock::new(VerificationState {
680 dec_keys_by_kid: decoding_keys_by_kid,
681 fallback_dec_key: fallback_decoding_key,
682 })),
683 header,
684 validation,
685 phantom_payload: PhantomData,
686 }
687 }
688
689 pub fn key_id(&self) -> &str {
691 &self.key_id
692 }
693
694 pub fn verification_key_count(&self) -> usize {
696 self.verification_state
697 .read()
698 .map(|state| state.dec_keys_by_kid.len())
699 .unwrap_or(0)
700 }
701
702 pub fn has_verification_key(&self, kid: &str) -> bool {
704 self.verification_state
705 .read()
706 .map(|state| state.dec_keys_by_kid.contains_key(kid))
707 .unwrap_or(false)
708 }
709
710 pub fn allows_missing_kid_fallback(&self) -> bool {
712 self.verification_state
713 .read()
714 .map(|state| state.fallback_dec_key.is_some())
715 .unwrap_or(false)
716 }
717
718 pub fn replace_verification_keys(
720 &self,
721 keys: HashMap<String, DecodingKey>,
722 allow_missing_kid_fallback: bool,
723 ) {
724 if let Ok(mut state) = self.verification_state.write() {
725 let fallback = if allow_missing_kid_fallback {
726 keys.values().next().cloned()
727 } else {
728 None
729 };
730 state.dec_keys_by_kid = keys;
731 state.fallback_dec_key = fallback;
732 }
733 }
734
735 pub fn replace_verification_keys_from_jwks(
741 &self,
742 keys: &[jwks::EcP384Jwk],
743 allow_missing_kid_fallback: bool,
744 ) -> Result<()> {
745 let mut decoding_keys = HashMap::new();
746 for key in keys {
747 let decoding_key = key.to_decoding_key()?;
748 decoding_keys.insert(key.kid.clone(), decoding_key);
749 }
750 self.replace_verification_keys(decoding_keys, allow_missing_kid_fallback);
751 Ok(())
752 }
753
754 fn decoding_key_for_header(&self, header: &Header) -> Result<DecodingKey> {
755 if header.alg != Algorithm::ES384 {
756 return Err(Error::Jwt(JwtError::processing(
757 JwtOperation::Validate,
758 format!(
759 "JWT header algorithm mismatch: expected ES384 but got {:?}",
760 header.alg
761 ),
762 )));
763 }
764
765 if header.typ.as_deref() != Some("JWT") {
766 return Err(Error::Jwt(JwtError::processing(
767 JwtOperation::Validate,
768 "JWT header `typ` must be `JWT`",
769 )));
770 }
771
772 let state = self.verification_state.read().map_err(|_| {
773 Error::Jwt(JwtError::processing(
774 JwtOperation::Validate,
775 "verification key state lock is poisoned",
776 ))
777 })?;
778
779 if let Some(kid) = header.kid.as_deref() {
780 return state.dec_keys_by_kid.get(kid).cloned().ok_or_else(|| {
781 Error::Jwt(JwtError::processing(
782 JwtOperation::Validate,
783 format!("JWT `kid` `{kid}` is not configured for verification"),
784 ))
785 });
786 }
787
788 state.fallback_dec_key.clone().ok_or_else(|| {
789 Error::Jwt(JwtError::processing(
790 JwtOperation::Validate,
791 "JWT header is missing `kid` and no fallback verification key is configured",
792 ))
793 })
794 }
795}
796
797impl<P> Default for JsonWebToken<P> {
798 fn default() -> Self {
799 Self::new_with_options(JsonWebTokenOptions::default())
800 }
801}
802
803impl<P> Codec for JsonWebToken<P>
804where
805 P: Serialize + DeserializeOwned + Clone,
806{
807 type Payload = P;
808
809 fn encode(&self, payload: &Self::Payload) -> Result<Vec<u8>> {
810 let Some(enc_key) = &self.enc_key else {
811 return Err(Error::Jwt(JwtError::processing(
812 JwtOperation::Encode,
813 "JWT encoding key is not configured for this codec",
814 )));
815 };
816 let token = jsonwebtoken::encode(&self.header, payload, enc_key).map_err(|error| {
817 Error::Jwt(JwtError::processing(
818 JwtOperation::Encode,
819 format!("JWT encoding failed: {error}"),
820 ))
821 })?;
822
823 Ok(token.into_bytes())
824 }
825
826 fn decode(&self, encoded_value: &[u8]) -> Result<Self::Payload> {
827 let header = decode_header(std::str::from_utf8(encoded_value).map_err(|error| {
828 Error::Jwt(JwtError::processing(
829 JwtOperation::Decode,
830 format!("JWT bytes are not valid UTF-8: {error}"),
831 ))
832 })?)
833 .map_err(|error| {
834 Error::Jwt(JwtError::processing_with_preview(
835 JwtOperation::Decode,
836 format!("JWT header decoding failed: {error}"),
837 Some(format!("token_len={}", encoded_value.len())),
838 ))
839 })?;
840
841 let decoding_key = self.decoding_key_for_header(&header)?;
842
843 let claims =
844 jsonwebtoken::decode::<Self::Payload>(encoded_value, &decoding_key, &self.validation)
845 .map_err(|error| {
846 Error::Jwt(JwtError::processing_with_preview(
850 JwtOperation::Decode,
851 format!("JWT decoding failed: {error}"),
852 Some(format!("token_len={}", encoded_value.len())),
853 ))
854 })?;
855
856 Ok(claims.claims)
857 }
858}