Skip to main content

jwt_simple/algorithms/jwe/
ecdh_es.rs

1//! ECDH-ES key agreement algorithms for JWE.
2//!
3//! Implements ECDH-ES+A256KW and ECDH-ES+A128KW (Elliptic Curve Diffie-Hellman
4//! Ephemeral Static key agreement with AES Key Wrap).
5
6use ct_codecs::{Base64UrlSafeNoPadding, Decoder, Encoder};
7use p256::ecdh::EphemeralSecret;
8use p256::elliptic_curve::sec1::{FromSec1Point, ToSec1Point};
9use p256::elliptic_curve::Generate as _;
10use p256::pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey, EncodePublicKey};
11use p256::{NonZeroScalar, PublicKey, Sec1Point, SecretKey};
12use rand::rng;
13use serde::{de::DeserializeOwned, Serialize};
14use serde_json::json;
15use zeroize::Zeroize;
16
17use crate::algorithms::jwe::aes_kw::{A128KWKey, A256KWKey};
18use crate::claims::*;
19use crate::error::*;
20use crate::jwe_header::JWEHeader;
21use crate::jwe_token::{DecryptionOptions, EncryptionOptions, JWEToken, JWETokenMetadata};
22
23/// Derive a key using Concat KDF as specified in NIST SP 800-56A.
24fn concat_kdf(
25    shared_secret: &[u8],
26    key_len: usize,
27    alg: &str,
28    apu: Option<&[u8]>,
29    apv: Option<&[u8]>,
30) -> Vec<u8> {
31    use hmac_sha256::Hash as SHA256;
32
33    let apu = apu.unwrap_or(&[]);
34    let apv = apv.unwrap_or(&[]);
35
36    // AlgorithmID || PartyUInfo || PartyVInfo || SuppPubInfo
37    let alg_bytes = alg.as_bytes();
38    let alg_len = (alg_bytes.len() as u32).to_be_bytes();
39    let apu_len = (apu.len() as u32).to_be_bytes();
40    let apv_len = (apv.len() as u32).to_be_bytes();
41    let key_bits = ((key_len * 8) as u32).to_be_bytes();
42
43    let mut derived_key = Vec::with_capacity(key_len);
44    let mut counter: u32 = 1;
45
46    while derived_key.len() < key_len {
47        let counter_bytes = counter.to_be_bytes();
48
49        // Hash: counter || Z || OtherInfo
50        let mut hasher = SHA256::new();
51        hasher.update(counter_bytes);
52        hasher.update(shared_secret);
53        // OtherInfo = AlgorithmID || PartyUInfo || PartyVInfo || SuppPubInfo
54        hasher.update(alg_len);
55        hasher.update(alg_bytes);
56        hasher.update(apu_len);
57        hasher.update(apu);
58        hasher.update(apv_len);
59        hasher.update(apv);
60        hasher.update(key_bits);
61
62        let hash = hasher.finalize();
63        derived_key.extend_from_slice(&hash);
64        counter += 1;
65    }
66
67    derived_key.truncate(key_len);
68    derived_key
69}
70
71/// P-256 public key for ECDH-ES+A256KW encryption.
72#[derive(Debug, Clone)]
73pub struct EcdhEsA256KWEncryptionKey {
74    pk: PublicKey,
75    key_id: Option<String>,
76}
77
78impl EcdhEsA256KWEncryptionKey {
79    const ALG_NAME: &'static str = "ECDH-ES+A256KW";
80    const KEY_WRAP_SIZE: usize = 32;
81
82    /// Create from SEC1-encoded bytes (compressed or uncompressed).
83    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
84        let point = Sec1Point::from_bytes(bytes).map_err(|_| JWTError::InvalidPublicKey)?;
85        let pk = PublicKey::from_sec1_point(&point);
86        if pk.is_none().into() {
87            bail!(JWTError::InvalidPublicKey);
88        }
89        Ok(EcdhEsA256KWEncryptionKey {
90            pk: pk.unwrap(),
91            key_id: None,
92        })
93    }
94
95    /// Create from DER-encoded public key.
96    pub fn from_der(der: &[u8]) -> Result<Self, Error> {
97        let pk = PublicKey::from_public_key_der(der).map_err(|_| JWTError::InvalidPublicKey)?;
98        Ok(EcdhEsA256KWEncryptionKey { pk, key_id: None })
99    }
100
101    /// Create from PEM-encoded public key.
102    pub fn from_pem(pem: &str) -> Result<Self, Error> {
103        let pk = PublicKey::from_public_key_pem(pem).map_err(|_| JWTError::InvalidPublicKey)?;
104        Ok(EcdhEsA256KWEncryptionKey { pk, key_id: None })
105    }
106
107    /// Export as SEC1 compressed bytes.
108    pub fn to_bytes(&self) -> Vec<u8> {
109        self.pk.to_sec1_point(true).as_bytes().to_vec()
110    }
111
112    /// Export as SEC1 uncompressed bytes.
113    pub fn to_bytes_uncompressed(&self) -> Vec<u8> {
114        self.pk.to_sec1_point(false).as_bytes().to_vec()
115    }
116
117    /// Export as DER.
118    pub fn to_der(&self) -> Result<Vec<u8>, Error> {
119        Ok(self
120            .pk
121            .to_public_key_der()
122            .map_err(|_| JWTError::InvalidPublicKey)?
123            .as_ref()
124            .to_vec())
125    }
126
127    /// Export as PEM.
128    pub fn to_pem(&self) -> Result<String, Error> {
129        Ok(self
130            .pk
131            .to_public_key_pem(Default::default())
132            .map_err(|_| JWTError::InvalidPublicKey)?)
133    }
134
135    /// Set the key ID.
136    pub fn with_key_id(mut self, key_id: impl Into<String>) -> Self {
137        self.key_id = Some(key_id.into());
138        self
139    }
140
141    /// Get the key ID.
142    pub fn key_id(&self) -> Option<&str> {
143        self.key_id.as_deref()
144    }
145
146    fn build_epk_jwk(&self, ephemeral_pk: &PublicKey) -> serde_json::Value {
147        let point = ephemeral_pk.to_sec1_point(false);
148        let x = Base64UrlSafeNoPadding::encode_to_string(point.x().unwrap()).unwrap();
149        let y = Base64UrlSafeNoPadding::encode_to_string(point.y().unwrap()).unwrap();
150        json!({
151            "kty": "EC",
152            "crv": "P-256",
153            "x": x,
154            "y": y
155        })
156    }
157
158    /// Encrypt claims into a JWE token.
159    pub fn encrypt<CustomClaims: Serialize>(
160        &self,
161        claims: JWTClaims<CustomClaims>,
162    ) -> Result<String, Error> {
163        self.encrypt_with_options(claims, &EncryptionOptions::default())
164    }
165
166    /// Encrypt claims into a JWE token with options.
167    pub fn encrypt_with_options<CustomClaims: Serialize>(
168        &self,
169        claims: JWTClaims<CustomClaims>,
170        options: &EncryptionOptions,
171    ) -> Result<String, Error> {
172        let content_encryption = options.content_encryption;
173
174        let ephemeral_secret = EphemeralSecret::generate_from_rng(&mut rng());
175        let ephemeral_pk = ephemeral_secret.public_key();
176
177        let shared_secret = ephemeral_secret.diffie_hellman(&self.pk);
178
179        let mut kek = concat_kdf(
180            shared_secret.raw_secret_bytes(),
181            Self::KEY_WRAP_SIZE,
182            Self::ALG_NAME,
183            None,
184            None,
185        );
186
187        let wrap_key = A256KWKey::from_bytes(&kek)?;
188        kek.zeroize();
189
190        let mut header = JWEHeader::new(Self::ALG_NAME, content_encryption.alg_name());
191        header.ephemeral_public_key = Some(self.build_epk_jwk(&ephemeral_pk));
192
193        if let Some(key_id) = &self.key_id {
194            header.key_id = Some(key_id.clone());
195        }
196        if let Some(key_id) = &options.key_id {
197            header.key_id = Some(key_id.clone());
198        }
199        if let Some(cty) = &options.content_type {
200            header.content_type = Some(cty.clone());
201        }
202
203        JWEToken::build_from_claims(&header, &claims, content_encryption, |cek| {
204            wrap_key.wrap_key(cek)
205        })
206    }
207}
208
209/// P-256 key pair for ECDH-ES+A256KW decryption.
210#[derive(Clone)]
211pub struct EcdhEsA256KWDecryptionKey {
212    sk: SecretKey,
213    key_id: Option<String>,
214}
215
216impl std::fmt::Debug for EcdhEsA256KWDecryptionKey {
217    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218        f.debug_struct("EcdhEsA256KWDecryptionKey")
219            .field("key_id", &self.key_id)
220            .finish_non_exhaustive()
221    }
222}
223
224impl EcdhEsA256KWDecryptionKey {
225    const ALG_NAME: &'static str = "ECDH-ES+A256KW";
226    const KEY_WRAP_SIZE: usize = 32;
227
228    /// Create from raw scalar bytes.
229    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
230        let sk = SecretKey::from_slice(bytes).map_err(|_| JWTError::InvalidKeyPair)?;
231        Ok(EcdhEsA256KWDecryptionKey { sk, key_id: None })
232    }
233
234    /// Create from DER-encoded private key.
235    pub fn from_der(der: &[u8]) -> Result<Self, Error> {
236        let sk = SecretKey::from_pkcs8_der(der).map_err(|_| JWTError::InvalidKeyPair)?;
237        Ok(EcdhEsA256KWDecryptionKey { sk, key_id: None })
238    }
239
240    /// Create from PEM-encoded private key.
241    pub fn from_pem(pem: &str) -> Result<Self, Error> {
242        let sk = SecretKey::from_pkcs8_pem(pem).map_err(|_| JWTError::InvalidKeyPair)?;
243        Ok(EcdhEsA256KWDecryptionKey { sk, key_id: None })
244    }
245
246    /// Generate a new key pair.
247    pub fn generate() -> Self {
248        let sk = SecretKey::generate_from_rng(&mut rng());
249        EcdhEsA256KWDecryptionKey { sk, key_id: None }
250    }
251
252    /// Export private key as raw bytes.
253    pub fn to_bytes(&self) -> Vec<u8> {
254        self.sk.to_bytes().to_vec()
255    }
256
257    /// Export private key as DER.
258    pub fn to_der(&self) -> Result<Vec<u8>, Error> {
259        let scalar = NonZeroScalar::from_repr(self.sk.to_bytes());
260        if bool::from(scalar.is_none()) {
261            return Err(JWTError::InvalidKeyPair.into());
262        }
263        let sk = SecretKey::from(NonZeroScalar::from_repr(scalar.unwrap().into()).unwrap());
264        Ok(sk
265            .to_pkcs8_der()
266            .map_err(|_| JWTError::InvalidKeyPair)?
267            .as_bytes()
268            .to_vec())
269    }
270
271    /// Export private key as PEM.
272    pub fn to_pem(&self) -> Result<String, Error> {
273        let scalar = NonZeroScalar::from_repr(self.sk.to_bytes());
274        if bool::from(scalar.is_none()) {
275            return Err(JWTError::InvalidKeyPair.into());
276        }
277        let sk = SecretKey::from(NonZeroScalar::from_repr(scalar.unwrap().into()).unwrap());
278        Ok(sk
279            .to_pkcs8_pem(Default::default())
280            .map_err(|_| JWTError::InvalidKeyPair)?
281            .to_string())
282    }
283
284    /// Get the public encryption key.
285    pub fn encryption_key(&self) -> EcdhEsA256KWEncryptionKey {
286        EcdhEsA256KWEncryptionKey {
287            pk: self.sk.public_key(),
288            key_id: self.key_id.clone(),
289        }
290    }
291
292    /// Set the key ID.
293    pub fn with_key_id(mut self, key_id: impl Into<String>) -> Self {
294        self.key_id = Some(key_id.into());
295        self
296    }
297
298    /// Get the key ID.
299    pub fn key_id(&self) -> Option<&str> {
300        self.key_id.as_deref()
301    }
302
303    fn parse_epk(epk: &serde_json::Value) -> Result<PublicKey, Error> {
304        let kty = epk.get("kty").and_then(|v| v.as_str());
305        ensure!(kty == Some("EC"), JWTError::InvalidEphemeralKey);
306
307        let crv = epk.get("crv").and_then(|v| v.as_str());
308        ensure!(crv == Some("P-256"), JWTError::InvalidEphemeralKey);
309
310        let x = epk
311            .get("x")
312            .and_then(|v| v.as_str())
313            .ok_or(JWTError::InvalidEphemeralKey)?;
314        let y = epk
315            .get("y")
316            .and_then(|v| v.as_str())
317            .ok_or(JWTError::InvalidEphemeralKey)?;
318
319        let x_bytes = Base64UrlSafeNoPadding::decode_to_vec(x, None)
320            .map_err(|_| JWTError::InvalidEphemeralKey)?;
321        let y_bytes = Base64UrlSafeNoPadding::decode_to_vec(y, None)
322            .map_err(|_| JWTError::InvalidEphemeralKey)?;
323
324        // Build uncompressed point: 0x04 || x || y
325        let mut point_bytes = vec![0x04];
326        point_bytes.extend_from_slice(&x_bytes);
327        point_bytes.extend_from_slice(&y_bytes);
328
329        let point =
330            Sec1Point::from_bytes(&point_bytes).map_err(|_| JWTError::InvalidEphemeralKey)?;
331        let pk = PublicKey::from_sec1_point(&point);
332        if pk.is_none().into() {
333            bail!(JWTError::InvalidEphemeralKey);
334        }
335
336        Ok(pk.unwrap())
337    }
338
339    /// Encrypt claims into a JWE token.
340    pub fn encrypt<CustomClaims: Serialize>(
341        &self,
342        claims: JWTClaims<CustomClaims>,
343    ) -> Result<String, Error> {
344        self.encryption_key().encrypt(claims)
345    }
346
347    /// Encrypt claims into a JWE token with options.
348    pub fn encrypt_with_options<CustomClaims: Serialize>(
349        &self,
350        claims: JWTClaims<CustomClaims>,
351        options: &EncryptionOptions,
352    ) -> Result<String, Error> {
353        self.encryption_key().encrypt_with_options(claims, options)
354    }
355
356    /// Decrypt a JWE token and return the claims.
357    ///
358    /// Decryption does not authenticate the sender: the encryption key is public,
359    /// so anyone can mint a token that decrypts successfully, with any claims.
360    /// Do not treat the result as trusted input without a separate signature check.
361    pub fn decrypt_token<CustomClaims: DeserializeOwned>(
362        &self,
363        token: &str,
364        options: Option<DecryptionOptions>,
365    ) -> Result<JWTClaims<CustomClaims>, Error> {
366        JWEToken::decrypt(Self::ALG_NAME, token, options, |header, encrypted_key| {
367            let epk = header
368                .ephemeral_public_key
369                .as_ref()
370                .ok_or(JWTError::MissingEphemeralKey)?;
371            let ephemeral_pk = Self::parse_epk(epk)?;
372
373            let shared_secret =
374                p256::ecdh::diffie_hellman(self.sk.to_nonzero_scalar(), ephemeral_pk.as_affine());
375
376            let mut kek = concat_kdf(
377                shared_secret.raw_secret_bytes(),
378                Self::KEY_WRAP_SIZE,
379                Self::ALG_NAME,
380                None,
381                None,
382            );
383
384            let wrap_key = A256KWKey::from_bytes(&kek)?;
385            kek.zeroize();
386            wrap_key.unwrap_key(encrypted_key)
387        })
388    }
389
390    /// Decode token metadata without decrypting.
391    pub fn decode_metadata(token: &str) -> Result<JWETokenMetadata, Error> {
392        JWEToken::decode_metadata(token)
393    }
394}
395
396/// P-256 public key for ECDH-ES+A128KW encryption.
397#[derive(Debug, Clone)]
398pub struct EcdhEsA128KWEncryptionKey {
399    pk: PublicKey,
400    key_id: Option<String>,
401}
402
403impl EcdhEsA128KWEncryptionKey {
404    const ALG_NAME: &'static str = "ECDH-ES+A128KW";
405    const KEY_WRAP_SIZE: usize = 16;
406
407    /// Create from SEC1-encoded bytes (compressed or uncompressed).
408    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
409        let point = Sec1Point::from_bytes(bytes).map_err(|_| JWTError::InvalidPublicKey)?;
410        let pk = PublicKey::from_sec1_point(&point);
411        if pk.is_none().into() {
412            bail!(JWTError::InvalidPublicKey);
413        }
414        Ok(EcdhEsA128KWEncryptionKey {
415            pk: pk.unwrap(),
416            key_id: None,
417        })
418    }
419
420    /// Create from DER-encoded public key.
421    pub fn from_der(der: &[u8]) -> Result<Self, Error> {
422        let pk = PublicKey::from_public_key_der(der).map_err(|_| JWTError::InvalidPublicKey)?;
423        Ok(EcdhEsA128KWEncryptionKey { pk, key_id: None })
424    }
425
426    /// Create from PEM-encoded public key.
427    pub fn from_pem(pem: &str) -> Result<Self, Error> {
428        let pk = PublicKey::from_public_key_pem(pem).map_err(|_| JWTError::InvalidPublicKey)?;
429        Ok(EcdhEsA128KWEncryptionKey { pk, key_id: None })
430    }
431
432    /// Export as SEC1 compressed bytes.
433    pub fn to_bytes(&self) -> Vec<u8> {
434        self.pk.to_sec1_point(true).as_bytes().to_vec()
435    }
436
437    /// Export as DER.
438    pub fn to_der(&self) -> Result<Vec<u8>, Error> {
439        Ok(self
440            .pk
441            .to_public_key_der()
442            .map_err(|_| JWTError::InvalidPublicKey)?
443            .as_ref()
444            .to_vec())
445    }
446
447    /// Export as PEM.
448    pub fn to_pem(&self) -> Result<String, Error> {
449        Ok(self
450            .pk
451            .to_public_key_pem(Default::default())
452            .map_err(|_| JWTError::InvalidPublicKey)?)
453    }
454
455    /// Set the key ID.
456    pub fn with_key_id(mut self, key_id: impl Into<String>) -> Self {
457        self.key_id = Some(key_id.into());
458        self
459    }
460
461    /// Get the key ID.
462    pub fn key_id(&self) -> Option<&str> {
463        self.key_id.as_deref()
464    }
465
466    fn build_epk_jwk(&self, ephemeral_pk: &PublicKey) -> serde_json::Value {
467        let point = ephemeral_pk.to_sec1_point(false);
468        let x = Base64UrlSafeNoPadding::encode_to_string(point.x().unwrap()).unwrap();
469        let y = Base64UrlSafeNoPadding::encode_to_string(point.y().unwrap()).unwrap();
470        json!({
471            "kty": "EC",
472            "crv": "P-256",
473            "x": x,
474            "y": y
475        })
476    }
477
478    /// Encrypt claims into a JWE token.
479    pub fn encrypt<CustomClaims: Serialize>(
480        &self,
481        claims: JWTClaims<CustomClaims>,
482    ) -> Result<String, Error> {
483        self.encrypt_with_options(claims, &EncryptionOptions::default())
484    }
485
486    /// Encrypt claims into a JWE token with options.
487    pub fn encrypt_with_options<CustomClaims: Serialize>(
488        &self,
489        claims: JWTClaims<CustomClaims>,
490        options: &EncryptionOptions,
491    ) -> Result<String, Error> {
492        let content_encryption = options.content_encryption;
493
494        let ephemeral_secret = EphemeralSecret::generate_from_rng(&mut rng());
495        let ephemeral_pk = ephemeral_secret.public_key();
496
497        let shared_secret = ephemeral_secret.diffie_hellman(&self.pk);
498
499        let mut kek = concat_kdf(
500            shared_secret.raw_secret_bytes(),
501            Self::KEY_WRAP_SIZE,
502            Self::ALG_NAME,
503            None,
504            None,
505        );
506
507        let wrap_key = A128KWKey::from_bytes(&kek)?;
508        kek.zeroize();
509
510        let mut header = JWEHeader::new(Self::ALG_NAME, content_encryption.alg_name());
511        header.ephemeral_public_key = Some(self.build_epk_jwk(&ephemeral_pk));
512
513        if let Some(key_id) = &self.key_id {
514            header.key_id = Some(key_id.clone());
515        }
516        if let Some(key_id) = &options.key_id {
517            header.key_id = Some(key_id.clone());
518        }
519        if let Some(cty) = &options.content_type {
520            header.content_type = Some(cty.clone());
521        }
522
523        JWEToken::build_from_claims(&header, &claims, content_encryption, |cek| {
524            wrap_key.wrap_key(cek)
525        })
526    }
527}
528
529/// P-256 key pair for ECDH-ES+A128KW decryption.
530#[derive(Clone)]
531pub struct EcdhEsA128KWDecryptionKey {
532    sk: SecretKey,
533    key_id: Option<String>,
534}
535
536impl std::fmt::Debug for EcdhEsA128KWDecryptionKey {
537    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
538        f.debug_struct("EcdhEsA128KWDecryptionKey")
539            .field("key_id", &self.key_id)
540            .finish_non_exhaustive()
541    }
542}
543
544impl EcdhEsA128KWDecryptionKey {
545    const ALG_NAME: &'static str = "ECDH-ES+A128KW";
546    const KEY_WRAP_SIZE: usize = 16;
547
548    /// Create from raw scalar bytes.
549    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
550        let sk = SecretKey::from_slice(bytes).map_err(|_| JWTError::InvalidKeyPair)?;
551        Ok(EcdhEsA128KWDecryptionKey { sk, key_id: None })
552    }
553
554    /// Create from DER-encoded private key.
555    pub fn from_der(der: &[u8]) -> Result<Self, Error> {
556        let sk = SecretKey::from_pkcs8_der(der).map_err(|_| JWTError::InvalidKeyPair)?;
557        Ok(EcdhEsA128KWDecryptionKey { sk, key_id: None })
558    }
559
560    /// Create from PEM-encoded private key.
561    pub fn from_pem(pem: &str) -> Result<Self, Error> {
562        let sk = SecretKey::from_pkcs8_pem(pem).map_err(|_| JWTError::InvalidKeyPair)?;
563        Ok(EcdhEsA128KWDecryptionKey { sk, key_id: None })
564    }
565
566    /// Generate a new key pair.
567    pub fn generate() -> Self {
568        let sk = SecretKey::generate_from_rng(&mut rng());
569        EcdhEsA128KWDecryptionKey { sk, key_id: None }
570    }
571
572    /// Export private key as raw bytes.
573    pub fn to_bytes(&self) -> Vec<u8> {
574        self.sk.to_bytes().to_vec()
575    }
576
577    /// Export private key as DER.
578    pub fn to_der(&self) -> Result<Vec<u8>, Error> {
579        let scalar = NonZeroScalar::from_repr(self.sk.to_bytes());
580        if bool::from(scalar.is_none()) {
581            return Err(JWTError::InvalidKeyPair.into());
582        }
583        let sk = SecretKey::from(NonZeroScalar::from_repr(scalar.unwrap().into()).unwrap());
584        Ok(sk
585            .to_pkcs8_der()
586            .map_err(|_| JWTError::InvalidKeyPair)?
587            .as_bytes()
588            .to_vec())
589    }
590
591    /// Export private key as PEM.
592    pub fn to_pem(&self) -> Result<String, Error> {
593        let scalar = NonZeroScalar::from_repr(self.sk.to_bytes());
594        if bool::from(scalar.is_none()) {
595            return Err(JWTError::InvalidKeyPair.into());
596        }
597        let sk = SecretKey::from(NonZeroScalar::from_repr(scalar.unwrap().into()).unwrap());
598        Ok(sk
599            .to_pkcs8_pem(Default::default())
600            .map_err(|_| JWTError::InvalidKeyPair)?
601            .to_string())
602    }
603
604    /// Get the public encryption key.
605    pub fn encryption_key(&self) -> EcdhEsA128KWEncryptionKey {
606        EcdhEsA128KWEncryptionKey {
607            pk: self.sk.public_key(),
608            key_id: self.key_id.clone(),
609        }
610    }
611
612    /// Set the key ID.
613    pub fn with_key_id(mut self, key_id: impl Into<String>) -> Self {
614        self.key_id = Some(key_id.into());
615        self
616    }
617
618    /// Get the key ID.
619    pub fn key_id(&self) -> Option<&str> {
620        self.key_id.as_deref()
621    }
622
623    fn parse_epk(epk: &serde_json::Value) -> Result<PublicKey, Error> {
624        let kty = epk.get("kty").and_then(|v| v.as_str());
625        ensure!(kty == Some("EC"), JWTError::InvalidEphemeralKey);
626
627        let crv = epk.get("crv").and_then(|v| v.as_str());
628        ensure!(crv == Some("P-256"), JWTError::InvalidEphemeralKey);
629
630        let x = epk
631            .get("x")
632            .and_then(|v| v.as_str())
633            .ok_or(JWTError::InvalidEphemeralKey)?;
634        let y = epk
635            .get("y")
636            .and_then(|v| v.as_str())
637            .ok_or(JWTError::InvalidEphemeralKey)?;
638
639        let x_bytes = Base64UrlSafeNoPadding::decode_to_vec(x, None)
640            .map_err(|_| JWTError::InvalidEphemeralKey)?;
641        let y_bytes = Base64UrlSafeNoPadding::decode_to_vec(y, None)
642            .map_err(|_| JWTError::InvalidEphemeralKey)?;
643
644        // Build uncompressed point: 0x04 || x || y
645        let mut point_bytes = vec![0x04];
646        point_bytes.extend_from_slice(&x_bytes);
647        point_bytes.extend_from_slice(&y_bytes);
648
649        let point =
650            Sec1Point::from_bytes(&point_bytes).map_err(|_| JWTError::InvalidEphemeralKey)?;
651        let pk = PublicKey::from_sec1_point(&point);
652        if pk.is_none().into() {
653            bail!(JWTError::InvalidEphemeralKey);
654        }
655
656        Ok(pk.unwrap())
657    }
658
659    /// Encrypt claims into a JWE token.
660    pub fn encrypt<CustomClaims: Serialize>(
661        &self,
662        claims: JWTClaims<CustomClaims>,
663    ) -> Result<String, Error> {
664        self.encryption_key().encrypt(claims)
665    }
666
667    /// Encrypt claims into a JWE token with options.
668    pub fn encrypt_with_options<CustomClaims: Serialize>(
669        &self,
670        claims: JWTClaims<CustomClaims>,
671        options: &EncryptionOptions,
672    ) -> Result<String, Error> {
673        self.encryption_key().encrypt_with_options(claims, options)
674    }
675
676    /// Decrypt a JWE token and return the claims.
677    ///
678    /// Decryption does not authenticate the sender: the encryption key is public,
679    /// so anyone can mint a token that decrypts successfully, with any claims.
680    /// Do not treat the result as trusted input without a separate signature check.
681    pub fn decrypt_token<CustomClaims: DeserializeOwned>(
682        &self,
683        token: &str,
684        options: Option<DecryptionOptions>,
685    ) -> Result<JWTClaims<CustomClaims>, Error> {
686        JWEToken::decrypt(Self::ALG_NAME, token, options, |header, encrypted_key| {
687            let epk = header
688                .ephemeral_public_key
689                .as_ref()
690                .ok_or(JWTError::MissingEphemeralKey)?;
691            let ephemeral_pk = Self::parse_epk(epk)?;
692
693            let shared_secret =
694                p256::ecdh::diffie_hellman(self.sk.to_nonzero_scalar(), ephemeral_pk.as_affine());
695
696            let mut kek = concat_kdf(
697                shared_secret.raw_secret_bytes(),
698                Self::KEY_WRAP_SIZE,
699                Self::ALG_NAME,
700                None,
701                None,
702            );
703
704            let wrap_key = A128KWKey::from_bytes(&kek)?;
705            kek.zeroize();
706            wrap_key.unwrap_key(encrypted_key)
707        })
708    }
709
710    /// Decode token metadata without decrypting.
711    pub fn decode_metadata(token: &str) -> Result<JWETokenMetadata, Error> {
712        JWEToken::decode_metadata(token)
713    }
714}