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