Skip to main content

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