Skip to main content

moq_token/
key.rs

1use crate::error::KeyError;
2use crate::generate::generate;
3use crate::{Algorithm, Claims};
4use base64::Engine;
5use jsonwebtoken::{DecodingKey, EncodingKey, Header};
6use p256::elliptic_curve::SecretKey;
7use p256::elliptic_curve::pkcs8::EncodePrivateKey;
8use rsa::BigUint;
9use rsa::pkcs1::EncodeRsaPrivateKey;
10use serde::{Deserialize, Deserializer, Serialize, Serializer};
11use std::sync::OnceLock;
12use std::{collections::HashSet, fmt, path::Path as StdPath};
13
14/// Cryptographic operations that a key can perform.
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
16#[serde(rename_all = "camelCase")]
17pub enum KeyOperation {
18	Sign,
19	Verify,
20	Decrypt,
21	Encrypt,
22}
23
24/// <https://datatracker.ietf.org/doc/html/rfc7518#section-6>
25#[derive(Clone, Serialize, Deserialize)]
26#[serde(tag = "kty")]
27pub enum KeyMaterial {
28	/// <https://datatracker.ietf.org/doc/html/rfc7518#section-6.2>
29	EC {
30		#[serde(rename = "crv")]
31		curve: EllipticCurve,
32		/// The X-coordinate of an EC key
33		#[serde(serialize_with = "serialize_base64url", deserialize_with = "deserialize_base64url")]
34		x: Vec<u8>,
35		/// The Y-coordinate of an EC key
36		#[serde(serialize_with = "serialize_base64url", deserialize_with = "deserialize_base64url")]
37		y: Vec<u8>,
38		/// The private value of an EC key
39		#[serde(
40			default,
41			skip_serializing_if = "Option::is_none",
42			serialize_with = "serialize_base64url_optional",
43			deserialize_with = "deserialize_base64url_optional"
44		)]
45		d: Option<Vec<u8>>,
46	},
47	/// <https://datatracker.ietf.org/doc/html/rfc7518#section-6.3>
48	RSA {
49		#[serde(flatten)]
50		public: RsaPublicKey,
51		#[serde(flatten, skip_serializing_if = "Option::is_none")]
52		private: Option<RsaPrivateKey>,
53	},
54	/// <https://datatracker.ietf.org/doc/html/rfc7518#section-6.4>
55	#[serde(rename = "oct")]
56	OCT {
57		/// The secret key as base64url (unpadded). Must be at least 32 bytes once decoded.
58		#[serde(
59			rename = "k",
60			serialize_with = "serialize_base64url",
61			deserialize_with = "deserialize_base64url"
62		)]
63		secret: Vec<u8>,
64	},
65	/// <https://datatracker.ietf.org/doc/html/rfc8037#section-2>
66	OKP {
67		#[serde(rename = "crv")]
68		curve: EllipticCurve,
69		#[serde(serialize_with = "serialize_base64url", deserialize_with = "deserialize_base64url")]
70		x: Vec<u8>,
71		#[serde(
72			rename = "d",
73			default,
74			skip_serializing_if = "Option::is_none",
75			serialize_with = "serialize_base64url_optional",
76			deserialize_with = "deserialize_base64url_optional"
77		)]
78		d: Option<Vec<u8>>,
79	},
80}
81
82/// Supported elliptic curves for EC and OKP key types.
83///
84/// See <https://datatracker.ietf.org/doc/html/rfc7518#section-6.2.1.1>
85#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
86pub enum EllipticCurve {
87	#[serde(rename = "P-256")]
88	P256,
89	#[serde(rename = "P-384")]
90	P384,
91	// jsonwebtoken doesn't support the ES512 algorithm, so we can't implement this
92	// #[serde(rename = "P-521")]
93	// P521,
94	#[serde(rename = "Ed25519")]
95	Ed25519,
96}
97
98/// RSA public key parameters.
99///
100/// See <https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.1>
101#[derive(Clone, Serialize, Deserialize)]
102pub struct RsaPublicKey {
103	#[serde(serialize_with = "serialize_base64url", deserialize_with = "deserialize_base64url")]
104	pub n: Vec<u8>,
105	#[serde(serialize_with = "serialize_base64url", deserialize_with = "deserialize_base64url")]
106	pub e: Vec<u8>,
107}
108
109/// RSA private key parameters.
110///
111/// See <https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.2>
112#[derive(Clone, Serialize, Deserialize)]
113pub struct RsaPrivateKey {
114	#[serde(serialize_with = "serialize_base64url", deserialize_with = "deserialize_base64url")]
115	pub d: Vec<u8>,
116	#[serde(serialize_with = "serialize_base64url", deserialize_with = "deserialize_base64url")]
117	pub p: Vec<u8>,
118	#[serde(serialize_with = "serialize_base64url", deserialize_with = "deserialize_base64url")]
119	pub q: Vec<u8>,
120	#[serde(serialize_with = "serialize_base64url", deserialize_with = "deserialize_base64url")]
121	pub dp: Vec<u8>,
122	#[serde(serialize_with = "serialize_base64url", deserialize_with = "deserialize_base64url")]
123	pub dq: Vec<u8>,
124	#[serde(serialize_with = "serialize_base64url", deserialize_with = "deserialize_base64url")]
125	pub qi: Vec<u8>,
126	#[serde(skip_serializing_if = "Option::is_none")]
127	pub oth: Option<Vec<RsaAdditionalPrime>>,
128}
129
130/// Additional prime information for multi-prime RSA keys.
131#[derive(Clone, Serialize, Deserialize)]
132pub struct RsaAdditionalPrime {
133	#[serde(serialize_with = "serialize_base64url", deserialize_with = "deserialize_base64url")]
134	pub r: Vec<u8>,
135	#[serde(serialize_with = "serialize_base64url", deserialize_with = "deserialize_base64url")]
136	pub d: Vec<u8>,
137	#[serde(serialize_with = "serialize_base64url", deserialize_with = "deserialize_base64url")]
138	pub t: Vec<u8>,
139}
140
141/// JWK, almost to spec (<https://datatracker.ietf.org/doc/html/rfc7517>) but not quite the same
142/// because it's annoying to implement.
143///
144/// This is the serialized form of a key, with plain fields you can build and edit. It is not
145/// usable on its own: call [`import`](Self::import) to validate it and get a usable [`Key`], and
146/// [`Key::export`] to go back the other way. What that key may do is whatever `key_ops` allows,
147/// so a verify-only JWK imports fine and simply cannot sign.
148#[derive(Clone, Serialize, Deserialize)]
149#[serde(remote = "Self")]
150#[non_exhaustive]
151pub struct Jwk {
152	/// The algorithm used by the key.
153	#[serde(rename = "alg")]
154	pub algorithm: Algorithm,
155
156	/// The permitted operations, defaulting to sign and verify when `key_ops` is absent
157	/// (optional per RFC 7517 section 4.3).
158	#[serde(rename = "key_ops", default = "sign_verify")]
159	pub operations: HashSet<KeyOperation>,
160
161	/// The key material. Defaults to [`KeyMaterial::OCT`] when `kty` is absent.
162	#[serde(flatten)]
163	pub material: KeyMaterial,
164
165	/// The key ID, useful for rotating keys.
166	#[serde(skip_serializing_if = "Option::is_none")]
167	pub kid: Option<crate::KeyId>,
168
169	/// Optional authorization limits for tokens signed by this key.
170	#[serde(default, skip_serializing_if = "Option::is_none")]
171	pub scope: Option<crate::Scope>,
172}
173
174fn sign_verify() -> HashSet<KeyOperation> {
175	[KeyOperation::Sign, KeyOperation::Verify].into()
176}
177
178/// Matches the minimum `js/token` enforces, so a key that loads in one loads in the other.
179const MIN_OCT_SECRET_BYTES: usize = 32;
180
181impl Jwk {
182	/// A key that can both sign and verify, with no key ID or scope.
183	///
184	/// Set the remaining fields on the returned value. The struct is `#[non_exhaustive]`, so
185	/// building it this way keeps working as JWK parameters are added.
186	pub fn new(algorithm: Algorithm, material: KeyMaterial) -> Self {
187		Self {
188			algorithm,
189			operations: sign_verify(),
190			material,
191			kid: None,
192			scope: None,
193		}
194	}
195
196	/// Validate the parameters and import this as a usable [`Key`].
197	///
198	/// The inverse of [`Key::export`]. Named rather than only a `TryFrom` impl so the conversion
199	/// is discoverable from here, and `import`/`export` rather than `validate` because the
200	/// `validate` methods elsewhere in this crate check without converting.
201	pub fn import(self) -> crate::Result<Key> {
202		if let Some(scope) = &self.scope {
203			scope.validate()?;
204		}
205
206		if let KeyMaterial::OCT { secret } = &self.material
207			&& secret.len() < MIN_OCT_SECRET_BYTES
208		{
209			return Err(KeyError::SecretTooShort(MIN_OCT_SECRET_BYTES).into());
210		}
211
212		Ok(Key {
213			jwk: self,
214			decode: Default::default(),
215			encode: Default::default(),
216		})
217	}
218}
219
220impl<'de> Deserialize<'de> for Jwk {
221	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
222	where
223		D: Deserializer<'de>,
224	{
225		let mut value = serde_json::Value::deserialize(deserializer)?;
226
227		// Normally the "kty" parameter is required in a JWK: https://datatracker.ietf.org/doc/html/rfc7517#section-4.1
228		// But for backwards compatibility we need to default to "oct" because in a previous
229		// implementation the parameter was omitted, and we want to keep previously generated tokens valid
230		if let Some(obj) = value.as_object_mut()
231			&& !obj.contains_key("kty")
232		{
233			obj.insert("kty".to_string(), serde_json::Value::String("oct".to_string()));
234		}
235
236		Self::deserialize(value).map_err(serde::de::Error::custom)
237	}
238}
239
240impl Serialize for Jwk {
241	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
242	where
243		S: Serializer,
244	{
245		Self::serialize(self, serializer)
246	}
247}
248
249/// A validated key, ready to sign and verify tokens.
250///
251/// The fields are fixed at construction: derived crypto material is cached on first use, so a key
252/// that could be mutated would sign with stale material. Build one from a [`Jwk`], from
253/// [`Key::generate`], or by parsing with [`Key::from_str`], then use the builders to derive a new
254/// key rather than editing an existing one.
255#[derive(Clone)]
256pub struct Key {
257	jwk: Jwk,
258
259	// Cached for performance reasons, unfortunately.
260	decode: OnceLock<DecodingKey>,
261	encode: OnceLock<EncodingKey>,
262}
263
264/// Read-only access to the underlying [`Jwk`] fields (`key.algorithm`, `key.kid`, ...).
265///
266/// Deliberately no `DerefMut`: handing out `&mut Jwk` would let a caller change the algorithm or
267/// key material behind the cached crypto material, which is the bug this split exists to prevent.
268impl std::ops::Deref for Key {
269	type Target = Jwk;
270
271	fn deref(&self) -> &Self::Target {
272		&self.jwk
273	}
274}
275
276impl TryFrom<Jwk> for Key {
277	type Error = crate::Error;
278
279	fn try_from(jwk: Jwk) -> crate::Result<Self> {
280		jwk.import()
281	}
282}
283
284impl From<&Key> for Jwk {
285	fn from(key: &Key) -> Self {
286		key.export()
287	}
288}
289
290impl<'de> Deserialize<'de> for Key {
291	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
292	where
293		D: Deserializer<'de>,
294	{
295		// Call the trait impl explicitly: the bare path would resolve to the inherent method that
296		// serde's `remote = "Self"` generates, skipping the `kty` default above.
297		let jwk = <Jwk as Deserialize>::deserialize(deserializer)?;
298		Key::try_from(jwk).map_err(serde::de::Error::custom)
299	}
300}
301
302impl Serialize for Key {
303	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
304	where
305		S: Serializer,
306	{
307		Serialize::serialize(&Jwk::from(self), serializer)
308	}
309}
310
311impl fmt::Debug for Key {
312	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
313		f.debug_struct("Key")
314			.field("algorithm", &self.algorithm)
315			.field("operations", &self.operations)
316			.field("kid", &self.kid)
317			.field("scope", &self.scope)
318			.finish()
319	}
320}
321
322impl Key {
323	/// The serializable [`Jwk`] behind this key, cloned so editing it can't reach the original.
324	///
325	/// The inverse of [`Jwk::import`]. Use it to derive a variant: export, edit, import again.
326	/// Reading a single field needs no clone, since a [`Key`] derefs to its [`Jwk`].
327	pub fn export(&self) -> Jwk {
328		self.jwk.clone()
329	}
330
331	/// Parse a key from a string, auto-detecting JSON or base64url encoding.
332	#[allow(clippy::should_implement_trait)]
333	pub fn from_str(s: &str) -> crate::Result<Self> {
334		let s = s.trim();
335		if s.starts_with('{') {
336			Ok(serde_json::from_str(s)?)
337		} else {
338			let decoded = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(s)?;
339			let json = String::from_utf8(decoded)?;
340			Ok(serde_json::from_str(&json)?)
341		}
342	}
343
344	/// Load a key from a file, auto-detecting JSON or base64url encoding.
345	pub fn from_file<P: AsRef<StdPath>>(path: P) -> crate::Result<Self> {
346		let contents = std::fs::read_to_string(&path)?;
347		Self::from_str(&contents)
348	}
349
350	/// Async version of [`from_file`](Self::from_file), using `tokio::fs`.
351	#[cfg(feature = "tokio")]
352	pub async fn from_file_async<P: AsRef<StdPath>>(path: P) -> crate::Result<Self> {
353		let contents = tokio::fs::read_to_string(path).await?;
354		Self::from_str(&contents)
355	}
356
357	/// Encode the key as base64url-encoded JSON.
358	pub fn to_str(&self) -> crate::Result<String> {
359		let json = serde_json::to_string(self)?;
360		Ok(base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(json.as_bytes()))
361	}
362
363	/// Write the key to a file as base64url-encoded JSON.
364	pub fn to_file<P: AsRef<StdPath>>(&self, path: P) -> crate::Result<()> {
365		let encoded = self.to_str()?;
366		std::fs::write(path, encoded)?;
367		Ok(())
368	}
369
370	/// Derive a verify-only copy of this key, dropping the private material.
371	///
372	/// Fails for symmetric (`oct`) keys, which have no public half, and for a key that cannot
373	/// verify in the first place.
374	pub fn to_public(&self) -> crate::Result<Self> {
375		if !self.operations.contains(&KeyOperation::Verify) {
376			return Err(KeyError::VerifyUnsupported.into());
377		}
378
379		let material = match self.material {
380			KeyMaterial::RSA { ref public, .. } => KeyMaterial::RSA {
381				public: public.clone(),
382				private: None,
383			},
384			KeyMaterial::EC {
385				ref x,
386				ref y,
387				ref curve,
388				..
389			} => KeyMaterial::EC {
390				x: x.clone(),
391				y: y.clone(),
392				curve: curve.clone(),
393				d: None,
394			},
395			KeyMaterial::OCT { .. } => return Err(KeyError::NoPublicKey.into()),
396			KeyMaterial::OKP { ref x, ref curve, .. } => KeyMaterial::OKP {
397				x: x.clone(),
398				curve: curve.clone(),
399				d: None,
400			},
401		};
402
403		Ok(Self {
404			jwk: Jwk {
405				algorithm: self.algorithm,
406				operations: [KeyOperation::Verify].into(),
407				material,
408				kid: self.kid.clone(),
409				scope: self.scope.clone(),
410			},
411			decode: Default::default(),
412			encode: Default::default(),
413		})
414	}
415
416	/// Whether the key carries the private material signing actually needs.
417	///
418	/// `key_ops` says what a key is *permitted* to do, which a public JWK can still advertise.
419	pub(crate) fn has_signing_material(&self) -> bool {
420		match &self.material {
421			KeyMaterial::OCT { .. } => true,
422			KeyMaterial::EC { d, .. } => d.is_some(),
423			KeyMaterial::OKP { d, .. } => d.is_some(),
424			KeyMaterial::RSA { private, .. } => private.is_some(),
425		}
426	}
427
428	fn to_decoding_key(&self) -> crate::Result<&DecodingKey> {
429		if let Some(key) = self.decode.get() {
430			return Ok(key);
431		}
432
433		let decoding_key = match self.material {
434			KeyMaterial::OCT { ref secret } => match self.algorithm {
435				Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => DecodingKey::from_secret(secret),
436				_ => return Err(KeyError::InvalidAlgorithm.into()),
437			},
438			KeyMaterial::EC {
439				ref curve,
440				ref x,
441				ref y,
442				..
443			} => match curve {
444				EllipticCurve::P256 => {
445					if self.algorithm != Algorithm::ES256 {
446						return Err(KeyError::InvalidAlgorithmForCurve("P-256").into());
447					}
448					if x.len() != 32 || y.len() != 32 {
449						return Err(KeyError::InvalidCoordinateLength("P-256").into());
450					}
451
452					DecodingKey::from_ec_components(
453						base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(x).as_ref(),
454						base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(y).as_ref(),
455					)?
456				}
457				EllipticCurve::P384 => {
458					if self.algorithm != Algorithm::ES384 {
459						return Err(KeyError::InvalidAlgorithmForCurve("P-384").into());
460					}
461					if x.len() != 48 || y.len() != 48 {
462						return Err(KeyError::InvalidCoordinateLength("P-384").into());
463					}
464
465					DecodingKey::from_ec_components(
466						base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(x).as_ref(),
467						base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(y).as_ref(),
468					)?
469				}
470				_ => return Err(KeyError::InvalidCurve("EC").into()),
471			},
472			KeyMaterial::OKP { ref curve, ref x, .. } => match curve {
473				EllipticCurve::Ed25519 => {
474					if self.algorithm != Algorithm::EdDSA {
475						return Err(KeyError::InvalidAlgorithmForCurve("Ed25519").into());
476					}
477
478					DecodingKey::from_ed_components(
479						base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(x).as_ref(),
480					)?
481				}
482				_ => return Err(KeyError::InvalidCurve("OKP").into()),
483			},
484			KeyMaterial::RSA { ref public, .. } => {
485				DecodingKey::from_rsa_raw_components(public.n.as_ref(), public.e.as_ref())
486			}
487		};
488
489		Ok(self.decode.get_or_init(|| decoding_key))
490	}
491
492	fn to_encoding_key(&self) -> crate::Result<&EncodingKey> {
493		if let Some(key) = self.encode.get() {
494			return Ok(key);
495		}
496
497		let encoding_key = match self.material {
498			KeyMaterial::OCT { ref secret } => match self.algorithm {
499				Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => EncodingKey::from_secret(secret),
500				_ => return Err(KeyError::InvalidAlgorithm.into()),
501			},
502			KeyMaterial::EC { ref curve, ref d, .. } => {
503				let d = d.as_ref().ok_or(KeyError::MissingPrivateKey)?;
504
505				match curve {
506					EllipticCurve::P256 => {
507						let secret_key = SecretKey::<p256::NistP256>::from_slice(d)?;
508						let doc = secret_key.to_pkcs8_der()?;
509						EncodingKey::from_ec_der(doc.as_bytes())
510					}
511					EllipticCurve::P384 => {
512						let secret_key = SecretKey::<p384::NistP384>::from_slice(d)?;
513						let doc = secret_key.to_pkcs8_der()?;
514						EncodingKey::from_ec_der(doc.as_bytes())
515					}
516					_ => return Err(KeyError::InvalidCurve("EC").into()),
517				}
518			}
519			KeyMaterial::OKP {
520				ref curve,
521				ref d,
522				ref x,
523			} => {
524				let d = d.as_ref().ok_or(KeyError::MissingPrivateKey)?;
525
526				let key_pair =
527					aws_lc_rs::signature::Ed25519KeyPair::from_seed_and_public_key(d.as_slice(), x.as_slice())?;
528
529				match curve {
530					EllipticCurve::Ed25519 => EncodingKey::from_ed_der(key_pair.to_pkcs8()?.as_ref()),
531					_ => return Err(KeyError::InvalidCurve("OKP").into()),
532				}
533			}
534			KeyMaterial::RSA {
535				ref public,
536				ref private,
537			} => {
538				let n = BigUint::from_bytes_be(&public.n);
539				let e = BigUint::from_bytes_be(&public.e);
540				let private = private.as_ref().ok_or(KeyError::MissingPrivateKey)?;
541				let d = BigUint::from_bytes_be(&private.d);
542				let p = BigUint::from_bytes_be(&private.p);
543				let q = BigUint::from_bytes_be(&private.q);
544
545				let rsa = rsa::RsaPrivateKey::from_components(n, e, d, vec![p, q]);
546				let pem = rsa?.to_pkcs1_pem(rsa::pkcs1::LineEnding::LF);
547
548				EncodingKey::from_rsa_pem(pem?.as_bytes())?
549			}
550		};
551
552		Ok(self.encode.get_or_init(|| encoding_key))
553	}
554
555	/// Verify a token's signature with this key and return its claims.
556	///
557	/// Rejects an expired token (the `exp` claim) and one that grants nothing.
558	/// Scoping the claims to a connection path is a separate step; see
559	/// [`Claims::authorize`].
560	pub fn verify(&self, token: &str) -> crate::Result<Claims> {
561		if !self.operations.contains(&KeyOperation::Verify) {
562			return Err(KeyError::VerifyUnsupported.into());
563		}
564
565		let decode = self.to_decoding_key()?;
566
567		let mut validation = jsonwebtoken::Validation::new(self.algorithm.into());
568		validation.required_spec_claims = Default::default(); // Don't require exp, but still validate it if present
569		validation.validate_exp = false; // We validate exp ourselves to handle null values
570
571		let token = jsonwebtoken::decode::<Claims>(token, decode, &validation)?;
572
573		if let Some(exp) = token.claims.expires
574			&& exp < std::time::SystemTime::now()
575		{
576			return Err(crate::Error::TokenExpired);
577		}
578
579		token.claims.validate()?;
580		self.validate_scope(&token.claims)?;
581
582		Ok(token.claims)
583	}
584
585	/// Sign the claims with this key, returning the encoded token.
586	pub fn sign(&self, payload: &Claims) -> crate::Result<String> {
587		if !self.operations.contains(&KeyOperation::Sign) {
588			return Err(KeyError::SignUnsupported.into());
589		}
590
591		payload.validate()?;
592		self.validate_scope(payload)?;
593
594		let encode = self.to_encoding_key()?;
595
596		let mut header = Header::new(self.algorithm.into());
597		header.kid = self.kid.as_ref().map(|k| k.to_string());
598		let token = jsonwebtoken::encode(&header, &payload, encode)?;
599		Ok(token)
600	}
601
602	#[doc(hidden)]
603	#[deprecated(note = "renamed to Key::sign")]
604	pub fn encode(&self, payload: &Claims) -> crate::Result<String> {
605		self.sign(payload)
606	}
607
608	#[doc(hidden)]
609	#[deprecated(note = "renamed to Key::verify")]
610	pub fn decode(&self, token: &str) -> crate::Result<Claims> {
611		self.verify(token)
612	}
613
614	/// Generate a key pair for the given algorithm, returning the private and public keys.
615	pub fn generate(algorithm: Algorithm, id: Option<crate::KeyId>) -> crate::Result<Self> {
616		generate(algorithm, id)
617	}
618
619	/// Derive a key with an authorization scope attached, capping what its tokens may grant.
620	///
621	/// The scope is validated here, and it is the only way to set one, so a key can never carry a
622	/// scope that permits nothing.
623	pub fn with_scope(mut self, scope: crate::Scope) -> crate::Result<Self> {
624		scope.validate()?;
625		self.jwk.scope = Some(scope);
626		Ok(self)
627	}
628
629	/// Derive a key restricted to the given operations.
630	pub fn with_operations(mut self, operations: impl IntoIterator<Item = KeyOperation>) -> Self {
631		self.jwk.operations = operations.into_iter().collect();
632		self
633	}
634
635	fn validate_scope(&self, claims: &Claims) -> crate::Result<()> {
636		if let Some(scope) = &self.scope {
637			scope.validate()?;
638			if !scope.allows(claims) {
639				return Err(crate::Error::ScopeExceeded);
640			}
641		}
642		Ok(())
643	}
644}
645
646/// Serialize bytes as base64url without padding
647fn serialize_base64url<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
648where
649	S: Serializer,
650{
651	let encoded = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(bytes);
652	serializer.serialize_str(&encoded)
653}
654
655fn serialize_base64url_optional<S>(bytes: &Option<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
656where
657	S: Serializer,
658{
659	match bytes {
660		Some(b) => serialize_base64url(b, serializer),
661		None => serializer.serialize_none(),
662	}
663}
664
665/// Deserialize base64url string to bytes, supporting both padded and unpadded formats for backwards compatibility
666fn deserialize_base64url<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
667where
668	D: Deserializer<'de>,
669{
670	let s = String::deserialize(deserializer)?;
671
672	// Try to decode as unpadded base64url first (preferred format)
673	base64::engine::general_purpose::URL_SAFE_NO_PAD
674		.decode(&s)
675		.or_else(|_| {
676			// Fall back to padded base64url for backwards compatibility
677			base64::engine::general_purpose::URL_SAFE.decode(&s)
678		})
679		.map_err(serde::de::Error::custom)
680}
681
682fn deserialize_base64url_optional<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>
683where
684	D: Deserializer<'de>,
685{
686	let s: Option<String> = Option::deserialize(deserializer)?;
687	match s {
688		Some(s) => {
689			let decoded = base64::engine::general_purpose::URL_SAFE_NO_PAD
690				.decode(&s)
691				.or_else(|_| base64::engine::general_purpose::URL_SAFE.decode(&s))
692				.map_err(serde::de::Error::custom)?;
693			Ok(Some(decoded))
694		}
695		None => Ok(None),
696	}
697}
698
699#[cfg(test)]
700mod tests {
701	use super::*;
702	use std::time::{Duration, SystemTime};
703
704	fn create_test_key() -> Key {
705		let mut jwk = Jwk::new(
706			Algorithm::HS256,
707			KeyMaterial::OCT {
708				secret: b"test-secret-that-is-long-enough-for-hmac-sha256".to_vec(),
709			},
710		);
711		jwk.kid = Some(crate::KeyId::decode("test-key-1").unwrap());
712		jwk.import().unwrap()
713	}
714
715	fn create_test_claims() -> Claims {
716		Claims {
717			root: "test-path".to_string(),
718			publish: vec!["test-pub".into()],
719			subscribe: vec!["test-sub".into()],
720			expires: Some(SystemTime::now() + Duration::from_secs(3600)),
721			issued: Some(SystemTime::now()),
722		}
723	}
724
725	#[test]
726	fn test_key_from_str_valid() {
727		let key = create_test_key();
728		let json = key.to_str().unwrap();
729		let loaded_key = Key::from_str(&json).unwrap();
730
731		assert_eq!(loaded_key.algorithm, key.algorithm);
732		assert_eq!(loaded_key.operations, key.operations);
733		match (&loaded_key.material, &key.material) {
734			(KeyMaterial::OCT { secret: loaded_secret }, KeyMaterial::OCT { secret }) => {
735				assert_eq!(loaded_secret, secret);
736			}
737			_ => panic!("Expected OCT key"),
738		}
739		assert_eq!(loaded_key.kid, key.kid);
740	}
741
742	/// Tests whether Key::from_str() works for keys without a kty value to fall back to OCT
743	#[test]
744	fn test_key_oct_backwards_compatibility() {
745		let json = r#"{"alg":"HS256","key_ops":["sign","verify"],"k":"Fp8kipWUJeUFqeSqWym_tRC_tyI8z-QpqopIGrbrD68"}"#;
746		let key = Key::from_str(json);
747
748		assert!(key.is_ok());
749		let key = key.unwrap();
750
751		if let KeyMaterial::OCT { secret, .. } = &key.material {
752			let base64_key = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(secret);
753			assert_eq!(base64_key, "Fp8kipWUJeUFqeSqWym_tRC_tyI8z-QpqopIGrbrD68");
754		} else {
755			panic!("Expected OCT key");
756		}
757
758		let key_str = key.to_str().unwrap();
759
760		// Round-trip through from_str and verify fields
761		let loaded = Key::from_str(&key_str).unwrap();
762		assert_eq!(loaded.algorithm, Algorithm::HS256);
763		assert!(loaded.operations.contains(&KeyOperation::Sign));
764		assert!(loaded.operations.contains(&KeyOperation::Verify));
765		assert!(matches!(loaded.material, KeyMaterial::OCT { .. }));
766	}
767
768	#[test]
769	fn test_key_without_key_ops_defaults_sign_verify() {
770		let json = r#"{"kty":"oct","alg":"HS256","k":"Fp8kipWUJeUFqeSqWym_tRC_tyI8z-QpqopIGrbrD68","kid":"no-ops"}"#;
771		let key = Key::from_str(json).unwrap();
772
773		assert_eq!(key.operations, sign_verify());
774
775		let claims = create_test_claims();
776		let token = key.sign(&claims).unwrap();
777		let verified = key.verify(&token).unwrap();
778		assert_eq!(verified.root, claims.root);
779	}
780
781	#[test]
782	fn test_key_without_key_ops_round_trip() {
783		let json = r#"{"kty":"oct","alg":"HS256","k":"Fp8kipWUJeUFqeSqWym_tRC_tyI8z-QpqopIGrbrD68"}"#;
784		let key = Key::from_str(json).unwrap();
785
786		let serialized = serde_json::to_string(&key).unwrap();
787		let parsed: serde_json::Value = serde_json::from_str(&serialized).unwrap();
788		let ops = parsed["key_ops"].as_array().unwrap();
789		assert_eq!(ops.len(), 2);
790
791		let reloaded = Key::from_str(&serialized).unwrap();
792		assert_eq!(reloaded.operations, sign_verify());
793		assert_eq!(reloaded.algorithm, key.algorithm);
794	}
795
796	// Defaulting key_ops must not let a weak or truncated file parse into a working key.
797	#[test]
798	fn test_key_oct_secret_required_and_min_length() {
799		// Missing k entirely, the truncated-file case.
800		assert!(Key::from_str(r#"{"kty":"oct","alg":"HS256"}"#).is_err());
801		assert!(Key::from_str(r#"{"kty":"oct","alg":"HS256","k":""}"#).is_err());
802
803		// 16-byte secret, below the 32-byte minimum.
804		assert!(Key::from_str(r#"{"kty":"oct","alg":"HS256","k":"AAAAAAAAAAAAAAAAAAAAAA"}"#).is_err());
805
806		// A short secret is rejected however the Jwk was built, not just when deserialized.
807		let short = Jwk::new(Algorithm::HS256, KeyMaterial::OCT { secret: vec![0; 16] });
808		assert!(short.import().is_err());
809
810		// Exactly 32 bytes.
811		let key =
812			Key::from_str(r#"{"kty":"oct","alg":"HS256","k":"Fp8kipWUJeUFqeSqWym_tRC_tyI8z-QpqopIGrbrD68"}"#).unwrap();
813		let KeyMaterial::OCT { ref secret } = key.material else {
814			panic!("Expected OCT key");
815		};
816		assert_eq!(secret.len(), 32);
817	}
818
819	#[test]
820	fn test_key_without_key_ops_to_public() {
821		let json = r#"{"kty":"OKP","alg":"EdDSA","crv":"Ed25519","x":"UiU9fT_SdBBpkFtJPRCY0gX1jK_Dr9syYLFuEz4QUM4","d":"lm-L_PV3ksuQ-KrFBgFMDJqAZC3_Z6Z5UC4ZQY5OoDQ","kid":"defaulted"}"#;
822		let key = Key::from_str(json).unwrap();
823		assert_eq!(key.operations, sign_verify());
824
825		let public = key.to_public().unwrap();
826		assert_eq!(public.operations, [KeyOperation::Verify].into());
827	}
828
829	#[test]
830	fn test_key_from_str_invalid_json() {
831		let result = Key::from_str("invalid json");
832		assert!(result.is_err());
833	}
834
835	#[test]
836	fn test_key_to_str() {
837		let key = create_test_key();
838		let encoded = key.to_str().unwrap();
839
840		// Should be base64url, not raw JSON
841		assert!(!encoded.contains('{'));
842
843		// Round-trip through from_str
844		let loaded = Key::from_str(&encoded).unwrap();
845		assert_eq!(loaded.algorithm, Algorithm::HS256);
846		assert_eq!(loaded.kid, key.kid);
847		assert!(loaded.operations.contains(&KeyOperation::Sign));
848		assert!(loaded.operations.contains(&KeyOperation::Verify));
849	}
850
851	#[test]
852	fn test_key_sign_success() {
853		let key = create_test_key();
854		let claims = create_test_claims();
855		let token = key.sign(&claims).unwrap();
856
857		assert!(!token.is_empty());
858		assert_eq!(token.matches('.').count(), 2); // JWT format: header.payload.signature
859	}
860
861	#[test]
862	fn test_key_scope_enforced_when_signing_and_verifying() {
863		let unrestricted = create_test_key();
864		let scoped = unrestricted
865			.clone()
866			.with_scope(crate::Scope {
867				root: "test-path".into(),
868				publish: vec!["allowed".into()],
869				subscribe: vec![],
870			})
871			.unwrap();
872		let allowed = Claims {
873			root: "test-path".into(),
874			publish: vec!["allowed/room".into()],
875			..Default::default()
876		};
877		let denied = Claims {
878			root: "test-path".into(),
879			publish: vec!["other".into()],
880			..Default::default()
881		};
882
883		assert!(scoped.sign(&allowed).is_ok());
884		assert!(matches!(scoped.sign(&denied), Err(crate::Error::ScopeExceeded)));
885
886		let forged = unrestricted.sign(&denied).unwrap();
887		assert!(matches!(scoped.verify(&forged), Err(crate::Error::ScopeExceeded)));
888	}
889
890	/// A key's crypto material is derived once and cached, so the fields it was derived from must
891	/// stay fixed. Changing the algorithm means building a new key, which derives fresh material.
892	#[test]
893	fn test_key_derived_material_never_stale() {
894		let claims = Claims {
895			root: "test-path".into(),
896			publish: vec!["test-pub".into()],
897			..Default::default()
898		};
899
900		// Sign once so the encode/decode caches are populated.
901		let key = create_test_key();
902		let token = key.sign(&claims).unwrap();
903		assert!(key.encode.get().is_some());
904
905		// The only way to change the algorithm is to build another key, which starts with an empty
906		// cache and therefore signs with material matching the header it writes.
907		let mut jwk = Jwk::from(&key);
908		jwk.algorithm = Algorithm::HS384;
909		let derived = Key::try_from(jwk).unwrap();
910		assert!(derived.encode.get().is_none());
911
912		let derived_token = derived.sign(&claims).unwrap();
913		assert_ne!(token, derived_token);
914
915		// The derived key agrees with one parsed cold from the same JWK, and the original key
916		// rejects the token it did not sign.
917		let cold = Key::from_str(&derived.to_str().unwrap()).unwrap();
918		assert_eq!(derived_token, cold.sign(&claims).unwrap());
919		assert!(cold.verify(&derived_token).is_ok());
920		assert!(key.verify(&derived_token).is_err());
921	}
922
923	/// A scope can only be attached through the validating builder, and the serde path validates
924	/// too, so a key can never carry a scope that grants nothing.
925	#[test]
926	fn test_key_scope_requires_validation() {
927		let key = create_test_key();
928		assert!(key.scope.is_none());
929
930		let useless = crate::Scope::default();
931		assert!(matches!(
932			key.clone().with_scope(useless.clone()),
933			Err(crate::Error::UselessScope)
934		));
935
936		let mut jwk = Jwk::from(&key);
937		jwk.scope = Some(useless);
938		assert!(matches!(Key::try_from(jwk), Err(crate::Error::UselessScope)));
939
940		let json = r#"{"alg":"HS256","key_ops":["sign"],"k":"Fp8kipWUJeUFqeSqWym_tRC_tyI8z-QpqopIGrbrD68","scope":{}}"#;
941		assert!(Key::from_str(json).is_err());
942	}
943
944	#[test]
945	fn test_key_sign_no_permission() {
946		let key = create_test_key().with_operations([KeyOperation::Verify]);
947		let claims = create_test_claims();
948
949		let result = key.sign(&claims);
950		assert!(result.is_err());
951		assert!(result.unwrap_err().to_string().contains("key does not support signing"));
952	}
953
954	#[test]
955	fn test_key_sign_invalid_claims() {
956		let key = create_test_key();
957		let invalid_claims = Claims {
958			root: "test-path".to_string(),
959			publish: vec![],
960			subscribe: vec![],
961			expires: None,
962			issued: None,
963		};
964
965		let result = key.sign(&invalid_claims);
966		assert!(result.is_err());
967		assert!(
968			result
969				.unwrap_err()
970				.to_string()
971				.contains("no publish or subscribe allowed; token is useless")
972		);
973	}
974
975	#[test]
976	fn test_key_verify_success() {
977		let key = create_test_key();
978		let claims = create_test_claims();
979		let token = key.sign(&claims).unwrap();
980
981		let verified_claims = key.verify(&token).unwrap();
982		assert_eq!(verified_claims.root, claims.root);
983		assert_eq!(verified_claims.publish, claims.publish);
984		assert_eq!(verified_claims.subscribe, claims.subscribe);
985	}
986
987	#[test]
988	fn test_key_verify_no_permission() {
989		let key = create_test_key().with_operations([KeyOperation::Sign]);
990
991		let result = key.verify("some.jwt.token");
992		assert!(result.is_err());
993		assert!(
994			result
995				.unwrap_err()
996				.to_string()
997				.contains("key does not support verification")
998		);
999	}
1000
1001	#[test]
1002	fn test_key_verify_invalid_token() {
1003		let key = create_test_key();
1004		let result = key.verify("invalid-token");
1005		assert!(result.is_err());
1006	}
1007
1008	#[test]
1009	fn test_key_verify_path_mismatch() {
1010		let key = create_test_key();
1011		let claims = create_test_claims();
1012		let token = key.sign(&claims).unwrap();
1013
1014		// This test was expecting a path mismatch error, but now decode succeeds
1015		let result = key.verify(&token);
1016		assert!(result.is_ok());
1017	}
1018
1019	#[test]
1020	fn test_key_verify_expired_token() {
1021		let key = create_test_key();
1022		let mut claims = create_test_claims();
1023		claims.expires = Some(SystemTime::now() - Duration::from_secs(3600)); // 1 hour ago
1024		let token = key.sign(&claims).unwrap();
1025
1026		let result = key.verify(&token);
1027		assert!(result.is_err());
1028	}
1029
1030	#[test]
1031	fn test_key_verify_token_without_exp() {
1032		let key = create_test_key();
1033		let claims = Claims {
1034			root: "test-path".to_string(),
1035			publish: vec!["".to_string()],
1036			subscribe: vec!["".to_string()],
1037			expires: None,
1038			issued: None,
1039		};
1040		let token = key.sign(&claims).unwrap();
1041
1042		let verified_claims = key.verify(&token).unwrap();
1043		assert_eq!(verified_claims.root, claims.root);
1044		assert_eq!(verified_claims.publish, claims.publish);
1045		assert_eq!(verified_claims.subscribe, claims.subscribe);
1046		assert_eq!(verified_claims.expires, None);
1047	}
1048
1049	#[test]
1050	fn test_key_round_trip() {
1051		let key = create_test_key();
1052		let original_claims = Claims {
1053			root: "test-path".to_string(),
1054			publish: vec!["test-pub".into()],
1055			subscribe: vec!["test-sub".into()],
1056			expires: Some(SystemTime::now() + Duration::from_secs(3600)),
1057			issued: Some(SystemTime::now()),
1058		};
1059
1060		let token = key.sign(&original_claims).unwrap();
1061		let verified_claims = key.verify(&token).unwrap();
1062
1063		assert_eq!(verified_claims.root, original_claims.root);
1064		assert_eq!(verified_claims.publish, original_claims.publish);
1065		assert_eq!(verified_claims.subscribe, original_claims.subscribe);
1066	}
1067
1068	#[test]
1069	fn test_key_generate_hs256() {
1070		let key = Key::generate(Algorithm::HS256, Some(crate::KeyId::decode("test-id").unwrap()));
1071		assert!(key.is_ok());
1072		let key = key.unwrap();
1073
1074		assert_eq!(key.algorithm, Algorithm::HS256);
1075		assert_eq!(key.kid, Some(crate::KeyId::decode("test-id").unwrap()));
1076		assert_eq!(key.operations, [KeyOperation::Sign, KeyOperation::Verify].into());
1077
1078		match &key.material {
1079			KeyMaterial::OCT { secret } => assert_eq!(secret.len(), 32),
1080			_ => panic!("Expected OCT key"),
1081		}
1082	}
1083
1084	#[test]
1085	fn test_key_generate_hs384() {
1086		let key = Key::generate(Algorithm::HS384, Some(crate::KeyId::decode("test-id").unwrap()));
1087		assert!(key.is_ok());
1088		let key = key.unwrap();
1089
1090		assert_eq!(key.algorithm, Algorithm::HS384);
1091
1092		match &key.material {
1093			KeyMaterial::OCT { secret } => assert_eq!(secret.len(), 48),
1094			_ => panic!("Expected OCT key"),
1095		}
1096	}
1097
1098	#[test]
1099	fn test_key_generate_hs512() {
1100		let key = Key::generate(Algorithm::HS512, Some(crate::KeyId::decode("test-id").unwrap()));
1101		assert!(key.is_ok());
1102		let key = key.unwrap();
1103
1104		assert_eq!(key.algorithm, Algorithm::HS512);
1105
1106		match &key.material {
1107			KeyMaterial::OCT { secret } => assert_eq!(secret.len(), 64),
1108			_ => panic!("Expected OCT key"),
1109		}
1110	}
1111
1112	#[test]
1113	fn test_key_generate_rs512() {
1114		let key = Key::generate(Algorithm::RS512, Some(crate::KeyId::decode("test-id").unwrap()));
1115		assert!(key.is_ok());
1116		let key = key.unwrap();
1117
1118		assert_eq!(key.algorithm, Algorithm::RS512);
1119		assert!(matches!(key.material, KeyMaterial::RSA { .. }));
1120		match &key.material {
1121			KeyMaterial::RSA { public, private } => {
1122				assert!(private.is_some());
1123				assert_eq!(public.n.len(), 256);
1124				assert_eq!(public.e.len(), 3);
1125			}
1126			_ => panic!("Expected RSA key"),
1127		}
1128	}
1129
1130	#[test]
1131	fn test_key_generate_es256() {
1132		let key = Key::generate(Algorithm::ES256, Some(crate::KeyId::decode("test-id").unwrap()));
1133		assert!(key.is_ok());
1134		let key = key.unwrap();
1135
1136		assert_eq!(key.algorithm, Algorithm::ES256);
1137		assert!(matches!(key.material, KeyMaterial::EC { .. }))
1138	}
1139
1140	#[test]
1141	fn test_key_generate_ps512() {
1142		let key = Key::generate(Algorithm::PS512, Some(crate::KeyId::decode("test-id").unwrap()));
1143		assert!(key.is_ok());
1144		let key = key.unwrap();
1145
1146		assert_eq!(key.algorithm, Algorithm::PS512);
1147		assert!(matches!(key.material, KeyMaterial::RSA { .. }));
1148	}
1149
1150	#[test]
1151	fn test_key_generate_eddsa() {
1152		let key = Key::generate(Algorithm::EdDSA, Some(crate::KeyId::decode("test-id").unwrap()));
1153		assert!(key.is_ok());
1154		let key = key.unwrap();
1155
1156		assert_eq!(key.algorithm, Algorithm::EdDSA);
1157		assert!(matches!(key.material, KeyMaterial::OKP { .. }));
1158	}
1159
1160	#[test]
1161	fn test_key_generate_without_id() {
1162		let key = Key::generate(Algorithm::HS256, None);
1163		assert!(key.is_ok());
1164		let key = key.unwrap();
1165
1166		assert_eq!(key.algorithm, Algorithm::HS256);
1167		assert_eq!(key.kid, None);
1168		assert_eq!(key.operations, [KeyOperation::Sign, KeyOperation::Verify].into());
1169	}
1170
1171	#[test]
1172	fn test_public_key_conversion_hmac() {
1173		let key = Key::generate(Algorithm::HS256, Some(crate::KeyId::decode("test-id").unwrap()))
1174			.expect("HMAC key generation failed");
1175
1176		assert!(key.to_public().is_err());
1177	}
1178
1179	#[test]
1180	fn test_public_key_conversion_rsa() {
1181		let key = Key::generate(Algorithm::RS256, Some(crate::KeyId::decode("test-id").unwrap()));
1182		assert!(key.is_ok());
1183		let key = key.unwrap();
1184
1185		let public_key = key.to_public().unwrap();
1186		assert_eq!(key.kid, public_key.kid);
1187		assert_eq!(public_key.operations, [KeyOperation::Verify].into());
1188		assert!(public_key.encode.get().is_none());
1189		assert!(public_key.decode.get().is_none());
1190		assert!(matches!(public_key.material, KeyMaterial::RSA { .. }));
1191
1192		if let KeyMaterial::RSA { public, private } = &public_key.material {
1193			assert!(private.is_none());
1194
1195			if let KeyMaterial::RSA { public: src_public, .. } = &key.material {
1196				assert_eq!(public.e, src_public.e);
1197				assert_eq!(public.n, src_public.n);
1198			} else {
1199				unreachable!("Expected RSA key")
1200			}
1201		} else {
1202			unreachable!("Expected RSA key");
1203		}
1204	}
1205
1206	#[test]
1207	fn test_public_key_conversion_es() {
1208		let key = Key::generate(Algorithm::ES256, Some(crate::KeyId::decode("test-id").unwrap()));
1209		assert!(key.is_ok());
1210		let key = key.unwrap();
1211
1212		let public_key = key.to_public().unwrap();
1213		assert_eq!(key.kid, public_key.kid);
1214		assert_eq!(public_key.operations, [KeyOperation::Verify].into());
1215		assert!(public_key.encode.get().is_none());
1216		assert!(public_key.decode.get().is_none());
1217		assert!(matches!(public_key.material, KeyMaterial::EC { .. }));
1218
1219		if let KeyMaterial::EC { x, y, d, curve } = &public_key.material {
1220			assert!(d.is_none());
1221
1222			if let KeyMaterial::EC {
1223				x: src_x,
1224				y: src_y,
1225				curve: src_curve,
1226				..
1227			} = &key.material
1228			{
1229				assert_eq!(x, src_x);
1230				assert_eq!(y, src_y);
1231				assert_eq!(curve, src_curve);
1232			} else {
1233				unreachable!("Expected EC key")
1234			}
1235		} else {
1236			unreachable!("Expected EC key");
1237		}
1238	}
1239
1240	#[test]
1241	fn test_public_key_conversion_ed() {
1242		let key = Key::generate(Algorithm::EdDSA, Some(crate::KeyId::decode("test-id").unwrap()));
1243		assert!(key.is_ok());
1244		let key = key.unwrap();
1245
1246		let public_key = key.to_public().unwrap();
1247		assert_eq!(key.kid, public_key.kid);
1248		assert_eq!(public_key.operations, [KeyOperation::Verify].into());
1249		assert!(public_key.encode.get().is_none());
1250		assert!(public_key.decode.get().is_none());
1251		assert!(matches!(public_key.material, KeyMaterial::OKP { .. }));
1252
1253		if let KeyMaterial::OKP { x, d, curve } = &public_key.material {
1254			assert!(d.is_none());
1255
1256			if let KeyMaterial::OKP {
1257				x: src_x,
1258				curve: src_curve,
1259				..
1260			} = &key.material
1261			{
1262				assert_eq!(x, src_x);
1263				assert_eq!(curve, src_curve);
1264			} else {
1265				unreachable!("Expected OKP key")
1266			}
1267		} else {
1268			unreachable!("Expected OKP key");
1269		}
1270	}
1271
1272	#[test]
1273	fn test_key_generate_sign_verify_cycle() {
1274		let key = Key::generate(Algorithm::HS256, Some(crate::KeyId::decode("test-id").unwrap()));
1275		assert!(key.is_ok());
1276		let key = key.unwrap();
1277
1278		let claims = create_test_claims();
1279
1280		let token = key.sign(&claims).unwrap();
1281		let verified_claims = key.verify(&token).unwrap();
1282
1283		assert_eq!(verified_claims.root, claims.root);
1284		assert_eq!(verified_claims.publish, claims.publish);
1285		assert_eq!(verified_claims.subscribe, claims.subscribe);
1286	}
1287
1288	#[test]
1289	fn test_key_debug_no_secret() {
1290		let key = create_test_key();
1291		let debug_str = format!("{key:?}");
1292
1293		assert!(debug_str.contains("algorithm: HS256"));
1294		assert!(debug_str.contains("operations"));
1295		assert!(debug_str.contains("kid: Some(KeyId(\"test-key-1\"))"));
1296		assert!(!debug_str.contains("secret")); // Should not contain secret
1297	}
1298
1299	#[test]
1300	fn test_key_operations_enum() {
1301		let sign_op = KeyOperation::Sign;
1302		let verify_op = KeyOperation::Verify;
1303		let decrypt_op = KeyOperation::Decrypt;
1304		let encrypt_op = KeyOperation::Encrypt;
1305
1306		assert_eq!(sign_op, KeyOperation::Sign);
1307		assert_eq!(verify_op, KeyOperation::Verify);
1308		assert_eq!(decrypt_op, KeyOperation::Decrypt);
1309		assert_eq!(encrypt_op, KeyOperation::Encrypt);
1310
1311		assert_ne!(sign_op, verify_op);
1312		assert_ne!(decrypt_op, encrypt_op);
1313	}
1314
1315	#[test]
1316	fn test_key_operations_serde() {
1317		let operations = [KeyOperation::Sign, KeyOperation::Verify];
1318		let json = serde_json::to_string(&operations).unwrap();
1319		assert!(json.contains("\"sign\""));
1320		assert!(json.contains("\"verify\""));
1321
1322		let deserialized: Vec<KeyOperation> = serde_json::from_str(&json).unwrap();
1323		assert_eq!(deserialized, operations);
1324	}
1325
1326	#[test]
1327	fn test_key_serde() {
1328		let key = create_test_key();
1329		let json = serde_json::to_string(&key).unwrap();
1330		let deserialized: Key = serde_json::from_str(&json).unwrap();
1331
1332		assert_eq!(deserialized.algorithm, key.algorithm);
1333		assert_eq!(deserialized.operations, key.operations);
1334		assert_eq!(deserialized.kid, key.kid);
1335
1336		if let (
1337			KeyMaterial::OCT {
1338				secret: original_secret,
1339			},
1340			KeyMaterial::OCT {
1341				secret: deserialized_secret,
1342			},
1343		) = (&key.material, &deserialized.material)
1344		{
1345			assert_eq!(deserialized_secret, original_secret);
1346		} else {
1347			panic!("Expected both keys to be OCT variant");
1348		}
1349	}
1350
1351	#[test]
1352	fn test_key_clone() {
1353		let key = create_test_key();
1354		let cloned = key.clone();
1355
1356		assert_eq!(cloned.algorithm, key.algorithm);
1357		assert_eq!(cloned.operations, key.operations);
1358		assert_eq!(cloned.kid, key.kid);
1359
1360		if let (
1361			KeyMaterial::OCT {
1362				secret: original_secret,
1363			},
1364			KeyMaterial::OCT { secret: cloned_secret },
1365		) = (&key.material, &cloned.material)
1366		{
1367			assert_eq!(cloned_secret, original_secret);
1368		} else {
1369			panic!("Expected both keys to be OCT variant");
1370		}
1371	}
1372
1373	#[test]
1374	fn test_hmac_algorithms() {
1375		let key_256 = Key::generate(Algorithm::HS256, Some(crate::KeyId::decode("test-id").unwrap()));
1376		let key_384 = Key::generate(Algorithm::HS384, Some(crate::KeyId::decode("test-id").unwrap()));
1377		let key_512 = Key::generate(Algorithm::HS512, Some(crate::KeyId::decode("test-id").unwrap()));
1378
1379		let claims = create_test_claims();
1380
1381		// Test that each algorithm can sign and verify
1382		for key in [key_256, key_384, key_512] {
1383			assert!(key.is_ok());
1384			let key = key.unwrap();
1385
1386			let token = key.sign(&claims).unwrap();
1387			let verified_claims = key.verify(&token).unwrap();
1388			assert_eq!(verified_claims.root, claims.root);
1389		}
1390	}
1391
1392	#[test]
1393	fn test_rsa_pkcs1_asymmetric_algorithms() {
1394		let key_rs256 = Key::generate(Algorithm::RS256, Some(crate::KeyId::decode("test-id").unwrap()));
1395		let key_rs384 = Key::generate(Algorithm::RS384, Some(crate::KeyId::decode("test-id").unwrap()));
1396		let key_rs512 = Key::generate(Algorithm::RS512, Some(crate::KeyId::decode("test-id").unwrap()));
1397
1398		for key in [key_rs256, key_rs384, key_rs512] {
1399			test_asymmetric_key(key);
1400		}
1401	}
1402
1403	#[test]
1404	fn test_rsa_pss_asymmetric_algorithms() {
1405		let key_ps256 = Key::generate(Algorithm::PS256, Some(crate::KeyId::decode("test-id").unwrap()));
1406		let key_ps384 = Key::generate(Algorithm::PS384, Some(crate::KeyId::decode("test-id").unwrap()));
1407		let key_ps512 = Key::generate(Algorithm::PS512, Some(crate::KeyId::decode("test-id").unwrap()));
1408
1409		for key in [key_ps256, key_ps384, key_ps512] {
1410			test_asymmetric_key(key);
1411		}
1412	}
1413
1414	#[test]
1415	fn test_ec_asymmetric_algorithms() {
1416		let key_es256 = Key::generate(Algorithm::ES256, Some(crate::KeyId::decode("test-id").unwrap()));
1417		let key_es384 = Key::generate(Algorithm::ES384, Some(crate::KeyId::decode("test-id").unwrap()));
1418
1419		for key in [key_es256, key_es384] {
1420			test_asymmetric_key(key);
1421		}
1422	}
1423
1424	#[test]
1425	fn test_ed_asymmetric_algorithms() {
1426		let key_eddsa = Key::generate(Algorithm::EdDSA, Some(crate::KeyId::decode("test-id").unwrap()));
1427
1428		test_asymmetric_key(key_eddsa);
1429	}
1430
1431	fn test_asymmetric_key(key: crate::Result<Key>) {
1432		assert!(key.is_ok());
1433		let key = key.unwrap();
1434
1435		let claims = create_test_claims();
1436		let token = key.sign(&claims).unwrap();
1437
1438		let private_verified_claims = key.verify(&token).unwrap();
1439		assert_eq!(
1440			private_verified_claims.root, claims.root,
1441			"validation using private key"
1442		);
1443
1444		let public_verified_claims = key.to_public().unwrap().verify(&token).unwrap();
1445		assert_eq!(public_verified_claims.root, claims.root, "validation using public key");
1446	}
1447
1448	#[test]
1449	fn test_cross_algorithm_verification_fails() {
1450		let key_256 = Key::generate(Algorithm::HS256, Some(crate::KeyId::decode("test-id").unwrap()));
1451		assert!(key_256.is_ok());
1452		let key_256 = key_256.unwrap();
1453
1454		let key_384 = Key::generate(Algorithm::HS384, Some(crate::KeyId::decode("test-id").unwrap()));
1455		assert!(key_384.is_ok());
1456		let key_384 = key_384.unwrap();
1457
1458		let claims = create_test_claims();
1459		let token = key_256.sign(&claims).unwrap();
1460
1461		// Different algorithm should fail verification
1462		let result = key_384.verify(&token);
1463		assert!(result.is_err());
1464	}
1465
1466	#[test]
1467	fn test_asymmetric_cross_algorithm_verification_fails() {
1468		let key_rs256 = Key::generate(Algorithm::RS256, Some(crate::KeyId::decode("test-id").unwrap()));
1469		assert!(key_rs256.is_ok());
1470		let key_rs256 = key_rs256.unwrap();
1471
1472		let key_ps256 = Key::generate(Algorithm::PS256, Some(crate::KeyId::decode("test-id").unwrap()));
1473		assert!(key_ps256.is_ok());
1474		let key_ps256 = key_ps256.unwrap();
1475
1476		let claims = create_test_claims();
1477		let token = key_rs256.sign(&claims).unwrap();
1478
1479		// Different algorithm should fail verification
1480		let private_result = key_ps256.verify(&token);
1481		let public_result = key_ps256.to_public().unwrap().verify(&token);
1482		assert!(private_result.is_err());
1483		assert!(public_result.is_err());
1484	}
1485
1486	#[test]
1487	fn test_rsa_pkcs1_public_key_conversion() {
1488		let key = Key::generate(Algorithm::RS256, Some(crate::KeyId::decode("test-id").unwrap()));
1489		assert!(key.is_ok());
1490		let key = key.unwrap();
1491
1492		assert!(key.operations.contains(&KeyOperation::Sign));
1493		assert!(key.operations.contains(&KeyOperation::Verify));
1494
1495		let public_key = key.to_public().unwrap();
1496		assert!(!public_key.operations.contains(&KeyOperation::Sign));
1497		assert!(public_key.operations.contains(&KeyOperation::Verify));
1498
1499		match &key.material {
1500			KeyMaterial::RSA { public, private } => {
1501				assert!(private.is_some());
1502				assert_eq!(public.n.len(), 256);
1503				assert_eq!(public.e.len(), 3);
1504
1505				match &public_key.material {
1506					KeyMaterial::RSA {
1507						public: guest_public,
1508						private: public_private,
1509					} => {
1510						assert!(public_private.is_none());
1511						assert_eq!(public.n, guest_public.n);
1512						assert_eq!(public.e, guest_public.e);
1513					}
1514					_ => panic!("Expected public key to be an RSA key"),
1515				}
1516			}
1517			_ => panic!("Expected private key to be an RSA key"),
1518		}
1519	}
1520
1521	#[test]
1522	fn test_rsa_pss_public_key_conversion() {
1523		let key = Key::generate(Algorithm::PS384, Some(crate::KeyId::decode("test-id").unwrap()));
1524		assert!(key.is_ok());
1525		let key = key.unwrap();
1526
1527		assert!(key.operations.contains(&KeyOperation::Sign));
1528		assert!(key.operations.contains(&KeyOperation::Verify));
1529
1530		let public_key = key.to_public().unwrap();
1531		assert!(!public_key.operations.contains(&KeyOperation::Sign));
1532		assert!(public_key.operations.contains(&KeyOperation::Verify));
1533
1534		match &key.material {
1535			KeyMaterial::RSA { public, private } => {
1536				assert!(private.is_some());
1537				assert_eq!(public.n.len(), 256);
1538				assert_eq!(public.e.len(), 3);
1539
1540				match &public_key.material {
1541					KeyMaterial::RSA {
1542						public: guest_public,
1543						private: public_private,
1544					} => {
1545						assert!(public_private.is_none());
1546						assert_eq!(public.n, guest_public.n);
1547						assert_eq!(public.e, guest_public.e);
1548					}
1549					_ => panic!("Expected public key to be an RSA key"),
1550				}
1551			}
1552			_ => panic!("Expected private key to be an RSA key"),
1553		}
1554	}
1555
1556	#[test]
1557	fn test_base64url_serialization() {
1558		let key = create_test_key();
1559		let json = serde_json::to_string(&key).unwrap();
1560
1561		// Check that the secret is base64url encoded without padding
1562		let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1563		let k_value = parsed["k"].as_str().unwrap();
1564
1565		// Base64url should not contain padding characters
1566		assert!(!k_value.contains('='));
1567		assert!(!k_value.contains('+'));
1568		assert!(!k_value.contains('/'));
1569
1570		// Verify it decodes correctly
1571		let decoded = base64::engine::general_purpose::URL_SAFE_NO_PAD
1572			.decode(k_value)
1573			.unwrap();
1574
1575		if let KeyMaterial::OCT {
1576			secret: original_secret,
1577		} = &key.material
1578		{
1579			assert_eq!(decoded, *original_secret);
1580		} else {
1581			panic!("Expected both keys to be OCT variant");
1582		}
1583	}
1584
1585	#[test]
1586	fn test_backwards_compatibility_unpadded_base64url() {
1587		// Create a JSON with unpadded base64url (new format)
1588		let unpadded_json = r#"{"kty":"oct","alg":"HS256","key_ops":["sign","verify"],"k":"dGVzdC1zZWNyZXQtdGhhdC1pcy1sb25nLWVub3VnaC1mb3ItaG1hYy1zaGEyNTY","kid":"test-key-1"}"#;
1589
1590		// Should be able to deserialize new format
1591		let key: Key = serde_json::from_str(unpadded_json).unwrap();
1592		assert_eq!(key.algorithm, Algorithm::HS256);
1593		assert_eq!(key.kid, Some(crate::KeyId::decode("test-key-1").unwrap()));
1594
1595		if let KeyMaterial::OCT { secret } = &key.material {
1596			assert_eq!(secret, b"test-secret-that-is-long-enough-for-hmac-sha256");
1597		} else {
1598			panic!("Expected key to be OCT variant");
1599		}
1600	}
1601
1602	#[test]
1603	fn test_backwards_compatibility_padded_base64url() {
1604		// Create a JSON with padded base64url (old format) - same secret but with padding
1605		let padded_json = r#"{"kty":"oct","alg":"HS256","key_ops":["sign","verify"],"k":"dGVzdC1zZWNyZXQtdGhhdC1pcy1sb25nLWVub3VnaC1mb3ItaG1hYy1zaGEyNTY=","kid":"test-key-1"}"#;
1606
1607		// Should be able to deserialize old format for backwards compatibility
1608		let key: Key = serde_json::from_str(padded_json).unwrap();
1609		assert_eq!(key.algorithm, Algorithm::HS256);
1610		assert_eq!(key.kid, Some(crate::KeyId::decode("test-key-1").unwrap()));
1611
1612		if let KeyMaterial::OCT { secret } = &key.material {
1613			assert_eq!(secret, b"test-secret-that-is-long-enough-for-hmac-sha256");
1614		} else {
1615			panic!("Expected key to be OCT variant");
1616		}
1617	}
1618
1619	// Tests that Rust can load keys generated by the JS @moq/token package
1620	// and verify tokens signed by JS.
1621	//
1622	// Generated with: bun -e 'import { generate } from "./js/token/src/generate.ts"; ...'
1623	// See js/token/src/interop.test.ts for the JS-side counterpart.
1624
1625	/// JS-generated HS256 key (from @moq/token generate("HS256", "js-test-key"))
1626	const JS_HS256_KEY: &str = r#"{"kty":"oct","alg":"HS256","k":"xm6xsSkfFqzPU3KfcbAcF2_h0OkStxQ_nNqVPYl0ync","kid":"js-test-key","key_ops":["sign","verify"],"guest":[],"guest_sub":[],"guest_pub":[]}"#;
1627
1628	/// JS-generated HS256 token (from @moq/token sign(key, {root:"live", put:["camera1"], get:["camera1","camera2"]}))
1629	const JS_HS256_TOKEN: &str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImpzLXRlc3Qta2V5In0.eyJyb290IjoibGl2ZSIsInB1dCI6WyJjYW1lcmExIl0sImdldCI6WyJjYW1lcmExIiwiY2FtZXJhMiJdLCJpYXQiOjE3NzUxNzY3NTR9.tHNQtHh_HCIKxXOexDCM7AkjqWzbULLZzjEckfOGRfY";
1630
1631	/// JS-generated EdDSA private key (from @moq/token generate("EdDSA", "js-eddsa-key"))
1632	const JS_EDDSA_PRIVATE_KEY: &str = r#"{"kty":"OKP","alg":"EdDSA","crv":"Ed25519","x":"UiU9fT_SdBBpkFtJPRCY0gX1jK_Dr9syYLFuEz4QUM4","d":"lm-L_PV3ksuQ-KrFBgFMDJqAZC3_Z6Z5UC4ZQY5OoDQ","kid":"js-eddsa-key","key_ops":["sign","verify"],"guest":[],"guest_sub":[],"guest_pub":[]}"#;
1633
1634	/// JS-generated EdDSA public key (from @moq/token toPublicKey(key))
1635	const JS_EDDSA_PUBLIC_KEY: &str = r#"{"kty":"OKP","alg":"EdDSA","crv":"Ed25519","x":"UiU9fT_SdBBpkFtJPRCY0gX1jK_Dr9syYLFuEz4QUM4","kid":"js-eddsa-key","guest":[],"guest_sub":[],"guest_pub":[],"key_ops":["verify"]}"#;
1636
1637	/// JS-generated EdDSA token (from @moq/token sign(key, {root:"stream", put:["video"]}))
1638	const JS_EDDSA_TOKEN: &str = "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImpzLWVkZHNhLWtleSJ9.eyJyb290Ijoic3RyZWFtIiwicHV0IjpbInZpZGVvIl0sImlhdCI6MTc3NTE3Njc1Nn0.l9rUMHjPSXWKSXRP3mmeMgTAywtqpdqQehhViWaPrKxax1Y2D9KRIYTixYz-b6PI-AoHQYusHWeeLu_HRw2cAg";
1639
1640	#[test]
1641	fn test_js_hs256_key_load() {
1642		let key = Key::from_str(JS_HS256_KEY).unwrap();
1643		assert_eq!(key.algorithm, Algorithm::HS256);
1644		assert_eq!(key.kid, Some(crate::KeyId::decode("js-test-key").unwrap()));
1645	}
1646
1647	#[test]
1648	fn test_js_hs256_token_verify() {
1649		let key = Key::from_str(JS_HS256_KEY).unwrap();
1650		let claims = key.verify(JS_HS256_TOKEN).unwrap();
1651		assert_eq!(claims.root, "live");
1652		assert_eq!(claims.publish, vec!["camera1"]);
1653		assert_eq!(claims.subscribe, vec!["camera1", "camera2"]);
1654	}
1655
1656	#[test]
1657	fn test_js_hs256_sign_and_roundtrip() {
1658		let key = Key::from_str(JS_HS256_KEY).unwrap();
1659		let claims = Claims {
1660			root: "rust-test".to_string(),
1661			publish: vec!["pub1".into()],
1662			subscribe: vec!["sub1".into()],
1663			..Default::default()
1664		};
1665		let token = key.sign(&claims).unwrap();
1666		let verified = key.verify(&token).unwrap();
1667		assert_eq!(verified.root, "rust-test");
1668		assert_eq!(verified.publish, vec!["pub1"]);
1669	}
1670
1671	#[test]
1672	fn test_js_eddsa_key_load() {
1673		let private_key = Key::from_str(JS_EDDSA_PRIVATE_KEY).unwrap();
1674		assert_eq!(private_key.algorithm, Algorithm::EdDSA);
1675		assert!(matches!(private_key.material, KeyMaterial::OKP { .. }));
1676
1677		let public_key = Key::from_str(JS_EDDSA_PUBLIC_KEY).unwrap();
1678		assert_eq!(public_key.algorithm, Algorithm::EdDSA);
1679	}
1680
1681	#[test]
1682	fn test_js_eddsa_token_verify_with_private_key() {
1683		let key = Key::from_str(JS_EDDSA_PRIVATE_KEY).unwrap();
1684		let claims = key.verify(JS_EDDSA_TOKEN).unwrap();
1685		assert_eq!(claims.root, "stream");
1686		assert_eq!(claims.publish, vec!["video"]);
1687	}
1688
1689	#[test]
1690	fn test_js_eddsa_token_verify_with_public_key() {
1691		let key = Key::from_str(JS_EDDSA_PUBLIC_KEY).unwrap();
1692		let claims = key.verify(JS_EDDSA_TOKEN).unwrap();
1693		assert_eq!(claims.root, "stream");
1694		assert_eq!(claims.publish, vec!["video"]);
1695	}
1696
1697	#[test]
1698	fn test_js_token_wrong_key_fails() {
1699		// Generate a different HS256 key
1700		let wrong_key = Key::generate(Algorithm::HS256, None).unwrap();
1701		let result = wrong_key.verify(JS_HS256_TOKEN);
1702		assert!(result.is_err());
1703	}
1704
1705	#[test]
1706	fn test_js_eddsa_token_wrong_key_fails() {
1707		// Try verifying EdDSA token with the HS256 key
1708		let wrong_key = Key::from_str(JS_HS256_KEY).unwrap();
1709		let result = wrong_key.verify(JS_EDDSA_TOKEN);
1710		assert!(result.is_err());
1711	}
1712
1713	#[test]
1714	fn test_file_io_base64url() {
1715		let key = create_test_key();
1716		let temp_dir = std::env::temp_dir();
1717		let temp_path = temp_dir.join("test_jwk.key");
1718
1719		// Write key to file as base64url
1720		key.to_file(&temp_path).unwrap();
1721
1722		// Read file contents
1723		let contents = std::fs::read_to_string(&temp_path).unwrap();
1724
1725		// Should be base64url encoded
1726		assert!(!contents.contains('{'));
1727		assert!(!contents.contains('}'));
1728		assert!(!contents.contains('"'));
1729
1730		// Decode and verify it's valid JSON
1731		let decoded = base64::engine::general_purpose::URL_SAFE_NO_PAD
1732			.decode(&contents)
1733			.unwrap();
1734		let json_str = String::from_utf8(decoded).unwrap();
1735		let _: serde_json::Value = serde_json::from_str(&json_str).unwrap();
1736
1737		// Read key back from file
1738		let loaded_key = Key::from_file(&temp_path).unwrap();
1739		assert_eq!(loaded_key.algorithm, key.algorithm);
1740		assert_eq!(loaded_key.operations, key.operations);
1741		assert_eq!(loaded_key.kid, key.kid);
1742
1743		if let (
1744			KeyMaterial::OCT {
1745				secret: original_secret,
1746			},
1747			KeyMaterial::OCT { secret: loaded_secret },
1748		) = (&key.material, &loaded_key.material)
1749		{
1750			assert_eq!(loaded_secret, original_secret);
1751		} else {
1752			panic!("Expected both keys to be OCT variant");
1753		}
1754
1755		// Clean up
1756		std::fs::remove_file(temp_path).ok();
1757	}
1758
1759	#[test]
1760	fn test_file_io_raw_json() {
1761		let key = create_test_key();
1762		let temp_dir = std::env::temp_dir();
1763		let temp_path = temp_dir.join("test_jwk_raw_json.key");
1764
1765		// Write key as raw JSON (backwards compat format)
1766		let json = serde_json::to_string(&key).unwrap();
1767		std::fs::write(&temp_path, &json).unwrap();
1768
1769		// Verify it looks like JSON
1770		assert!(json.starts_with('{'));
1771
1772		// Load via from_file (should auto-detect JSON)
1773		let loaded_key = Key::from_file(&temp_path).unwrap();
1774		assert_eq!(loaded_key.algorithm, key.algorithm);
1775		assert_eq!(loaded_key.operations, key.operations);
1776		assert_eq!(loaded_key.kid, key.kid);
1777
1778		if let (
1779			KeyMaterial::OCT {
1780				secret: original_secret,
1781			},
1782			KeyMaterial::OCT { secret: loaded_secret },
1783		) = (&key.material, &loaded_key.material)
1784		{
1785			assert_eq!(loaded_secret, original_secret);
1786		} else {
1787			panic!("Expected both keys to be OCT variant");
1788		}
1789
1790		// Clean up
1791		std::fs::remove_file(temp_path).ok();
1792	}
1793}