1#![no_std]
26#![cfg_attr(test, allow(clippy::indexing_slicing))]
29
30extern crate alloc;
31
32use alloc::collections::BTreeMap;
33use alloc::format;
34use alloc::string::String;
35use alloc::vec::Vec;
36use base64::Engine;
37use base64::engine::general_purpose::URL_SAFE_NO_PAD;
38use crypto_secretbox::aead::{Aead, KeyInit};
39use crypto_secretbox::{Key as SecretboxKey, Nonce as SecretboxNonce, XSalsa20Poly1305};
40use ed25519_dalek::{Signature, Verifier, VerifyingKey};
41use rand_core::TryRng;
42use salsa20::cipher::consts::{U10, U16};
43use salsa20::hsalsa;
44use serde::de::DeserializeOwned;
45use serde::{Deserialize, Serialize};
46use serde_json::Value;
47use sha2::{Digest, Sha512_256};
48use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret};
49use zeroize::Zeroizing;
50
51const HEADER_JSON: &str = r#"{"typ":"JWT","alg":"ed25519-nkey"}"#;
53const XKEY_VERSION_V1: &[u8; 4] = b"xkv1";
54const XKEY_NONCE_LEN: usize = 24;
55const XKEY_TAG_LEN: usize = 16;
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
76pub enum NkeyType {
77 Account,
79 Cluster,
81 Server,
83 Operator,
85 User,
87 Curve,
89}
90
91impl NkeyType {
92 const ALL: [Self; 6] = [
95 Self::Account,
96 Self::Cluster,
97 Self::Server,
98 Self::Operator,
99 Self::User,
100 Self::Curve,
101 ];
102
103 #[must_use]
105 pub const fn letter(self) -> char {
106 match self {
107 Self::Account => 'A',
108 Self::Cluster => 'C',
109 Self::Server => 'N',
110 Self::Operator => 'O',
111 Self::User => 'U',
112 Self::Curve => 'X',
113 }
114 }
115
116 #[must_use]
118 pub const fn prefix_byte(self) -> u8 {
119 match self {
121 Self::Account => 0, Self::Cluster => 2 << 3, Self::Server => 13 << 3, Self::Operator => 14 << 3, Self::User => 20 << 3, Self::Curve => 23 << 3, }
128 }
129
130 #[must_use]
133 pub fn from_letter(letter: char) -> Option<Self> {
134 Self::ALL.into_iter().find(|role| role.letter() == letter)
135 }
136
137 #[must_use]
140 fn from_prefix_byte(byte: u8) -> Option<Self> {
141 Self::ALL
142 .into_iter()
143 .find(|role| role.prefix_byte() == byte)
144 }
145}
146
147impl core::fmt::Display for NkeyType {
148 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
149 write!(f, "{}", self.letter())
150 }
151}
152
153#[derive(Debug)]
154pub enum Error {
155 BadEncoding,
157 BadPublicKeyLen(usize),
159 UnsupportedPrefix(char),
163 UnexpectedPrefix {
166 expected: NkeyType,
167 actual: NkeyType,
168 },
169 Json(serde_json::Error),
171 InvalidClaims(String),
173 MalformedJwt(String),
175 BadSignatureLen(usize),
178 UnexpectedXKeyPrefix(NkeyType),
180 BadXKeyVersion,
182 BadXKeyCiphertextLen(usize),
184 XKeyRandomnessFailed,
186 XKeySealFailed,
188 XKeyOpenFailed,
190}
191
192impl core::fmt::Display for Error {
193 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
194 match self {
195 Self::BadEncoding => f.write_str("invalid NKey base32 encoding"),
196 Self::BadPublicKeyLen(n) => write!(f, "expected a 32-byte Ed25519 public key, got {n}"),
197 Self::UnsupportedPrefix(c) => write!(f, "unsupported NKey prefix letter: {c}"),
198 Self::UnexpectedPrefix { expected, actual } => {
199 write!(f, "expected NKey role {expected}, got {actual}")
200 }
201 Self::Json(e) => write!(f, "json error: {e}"),
202 Self::InvalidClaims(message) | Self::MalformedJwt(message) => f.write_str(message),
203 Self::BadSignatureLen(n) => write!(f, "expected a 64-byte Ed25519 signature, got {n}"),
204 Self::UnexpectedXKeyPrefix(actual) => {
205 write!(f, "expected curve xkey prefix X, got {actual}")
206 }
207 Self::BadXKeyVersion => f.write_str("NATS xkey ciphertext has unsupported version"),
208 Self::BadXKeyCiphertextLen(n) => {
209 write!(f, "NATS xkey ciphertext is too short: {n} bytes")
210 }
211 Self::XKeyRandomnessFailed => f.write_str("NATS xkey randomness failed"),
212 Self::XKeySealFailed => f.write_str("NATS xkey seal failed"),
213 Self::XKeyOpenFailed => f.write_str("NATS xkey open failed"),
214 }
215 }
216}
217impl core::error::Error for Error {}
218impl From<serde_json::Error> for Error {
219 fn from(e: serde_json::Error) -> Self {
220 Self::Json(e)
221 }
222}
223
224fn crc16(data: &[u8]) -> u16 {
230 let mut crc: u16 = 0;
231 for &b in data {
232 crc ^= u16::from(b) << 8;
233 for _ in 0..8 {
234 crc = if crc & 0x8000 != 0 {
235 (crc << 1) ^ 0x1021
236 } else {
237 crc << 1
238 };
239 }
240 }
241 crc
242}
243
244fn encode_nkey(prefix: u8, public: &[u8]) -> Result<String, Error> {
245 if public.len() != 32 {
246 return Err(Error::BadPublicKeyLen(public.len()));
247 }
248 let mut raw = Vec::with_capacity(1 + 32 + 2);
249 raw.push(prefix);
250 raw.extend_from_slice(public);
251 let crc = crc16(&raw);
252 raw.push((crc & 0xff) as u8); raw.push((crc >> 8) as u8);
254 Ok(data_encoding::BASE32_NOPAD.encode(&raw))
255}
256
257pub fn encode_public(role: NkeyType, public: &[u8]) -> Result<String, Error> {
269 encode_nkey(role.prefix_byte(), public)
270}
271
272pub fn decode_public(nkey: &str) -> Result<(NkeyType, [u8; 32]), Error> {
283 let raw = data_encoding::BASE32_NOPAD
284 .decode(nkey.as_bytes())
285 .map_err(|_| Error::BadEncoding)?;
286 let Some((body, crc_bytes)) = raw.split_at_checked(33) else {
290 return Err(Error::BadPublicKeyLen(raw.len()));
291 };
292 let (Ok(crc_bytes), Some((&prefix, key))) =
293 (<[u8; 2]>::try_from(crc_bytes), body.split_first())
294 else {
295 return Err(Error::BadPublicKeyLen(raw.len()));
296 };
297 let Ok(key) = <[u8; 32]>::try_from(key) else {
298 return Err(Error::BadPublicKeyLen(raw.len()));
299 };
300 let crc = u16::from_le_bytes(crc_bytes);
301 if crc != crc16(body) {
302 return Err(Error::BadPublicKeyLen(raw.len()));
303 }
304 let role = NkeyType::from_prefix_byte(prefix)
305 .ok_or_else(|| Error::UnsupportedPrefix(nkey.chars().next().unwrap_or('?')))?;
306 Ok((role, key))
307}
308
309pub fn require_public_prefix(nkey: &str, expected: NkeyType) -> Result<[u8; 32], Error> {
319 let (actual, key) = decode_public(nkey)?;
320 if actual != expected {
321 return Err(Error::UnexpectedPrefix { expected, actual });
322 }
323 Ok(key)
324}
325
326pub fn verify_public_signature(
337 nkey: &str,
338 message: &[u8],
339 signature: &[u8],
340) -> Result<bool, Error> {
341 let (_, public_key) = decode_public(nkey)?;
342 let signature =
343 <[u8; 64]>::try_from(signature).map_err(|_| Error::BadSignatureLen(signature.len()))?;
344 Ok(verify_ed25519(&public_key, message, &signature))
345}
346
347#[must_use]
349pub fn xkey_public_from_private(private: &Zeroizing<[u8; 32]>) -> [u8; 32] {
350 let secret = StaticSecret::from(**private);
351 X25519PublicKey::from(&secret).to_bytes()
352}
353
354pub fn seal_nats_curve(
373 sender_private: &Zeroizing<[u8; 32]>,
374 recipient_public_xkey: &str,
375 plaintext: &[u8],
376 rng: &mut impl TryRng,
377) -> Result<Vec<u8>, Error> {
378 let recipient_public = decode_xkey_public(recipient_public_xkey)?;
379 let mut nonce = [0u8; XKEY_NONCE_LEN];
380 rng.try_fill_bytes(&mut nonce)
381 .map_err(|_| Error::XKeyRandomnessFailed)?;
382 let ciphertext = box_crypt(
383 sender_private,
384 &recipient_public,
385 &nonce,
386 plaintext,
387 BoxMode::Seal,
388 )?;
389 let mut out = Vec::with_capacity(XKEY_VERSION_V1.len() + nonce.len() + ciphertext.len());
390 out.extend_from_slice(XKEY_VERSION_V1);
391 out.extend_from_slice(&nonce);
392 out.extend_from_slice(&ciphertext);
393 Ok(out)
394}
395
396pub fn open_nats_curve(
404 recipient_private: &Zeroizing<[u8; 32]>,
405 sender_public_xkey: &str,
406 ciphertext: &[u8],
407) -> Result<Zeroizing<Vec<u8>>, Error> {
408 let sender_public = decode_xkey_public(sender_public_xkey)?;
409 let Some(rest) = ciphertext.strip_prefix(XKEY_VERSION_V1) else {
410 return Err(Error::BadXKeyVersion);
411 };
412 if rest.len() < XKEY_NONCE_LEN + XKEY_TAG_LEN {
414 return Err(Error::BadXKeyCiphertextLen(ciphertext.len()));
415 }
416 let Some((nonce, body)) = rest.split_at_checked(XKEY_NONCE_LEN) else {
417 return Err(Error::BadXKeyCiphertextLen(ciphertext.len()));
418 };
419 let nonce: [u8; XKEY_NONCE_LEN] = nonce
420 .try_into()
421 .map_err(|_| Error::BadXKeyCiphertextLen(ciphertext.len()))?;
422 box_crypt(
423 recipient_private,
424 &sender_public,
425 &nonce,
426 body,
427 BoxMode::Open,
428 )
429 .map(Zeroizing::new)
430}
431
432fn decode_xkey_public(nkey: &str) -> Result<[u8; 32], Error> {
433 let (actual, key) = decode_public(nkey)?;
434 if actual != NkeyType::Curve {
435 return Err(Error::UnexpectedXKeyPrefix(actual));
436 }
437 Ok(key)
438}
439
440#[derive(Clone, Copy)]
441enum BoxMode {
442 Seal,
443 Open,
444}
445
446fn box_crypt(
447 private: &Zeroizing<[u8; 32]>,
448 peer_public: &[u8; 32],
449 nonce: &[u8; XKEY_NONCE_LEN],
450 input: &[u8],
451 mode: BoxMode,
452) -> Result<Vec<u8>, Error> {
453 let private = StaticSecret::from(**private);
454 let peer = X25519PublicKey::from(*peer_public);
455 let shared_secret = private.diffie_hellman(&peer);
456 if !shared_secret.was_contributory() {
457 return match mode {
458 BoxMode::Seal => Err(Error::XKeySealFailed),
459 BoxMode::Open => Err(Error::XKeyOpenFailed),
460 };
461 }
462 let shared = Zeroizing::new(shared_secret.to_bytes());
463 let key = nats_box_key(&shared);
464 let cipher = XSalsa20Poly1305::new(&key);
465 let nonce = SecretboxNonce::from(*nonce);
466 match mode {
467 BoxMode::Seal => cipher
468 .encrypt(&nonce, input)
469 .map_err(|_| Error::XKeySealFailed),
470 BoxMode::Open => cipher
471 .decrypt(&nonce, input)
472 .map_err(|_| Error::XKeyOpenFailed),
473 }
474}
475
476#[allow(deprecated)]
477fn nats_box_key(shared: &Zeroizing<[u8; 32]>) -> Zeroizing<SecretboxKey> {
478 let input = crypto_secretbox::aead::generic_array::GenericArray::<u8, U16>::default();
479 let key = Zeroizing::new(SecretboxKey::clone_from_slice(shared.as_slice()));
480 Zeroizing::new(hsalsa::<U10>(&key, &input))
481}
482
483#[derive(Deserialize)]
488struct CompactHeader {
489 typ: String,
490 alg: String,
491}
492
493#[derive(Deserialize)]
494struct CompactClaims {
495 iss: String,
496 sub: String,
497 #[serde(default)]
498 iat: Option<u64>,
499 #[serde(default)]
500 exp: Option<u64>,
501 #[serde(default)]
502 nbf: Option<u64>,
503 #[serde(default)]
504 nats: Option<CompactNatsClaims>,
505}
506
507#[derive(Deserialize)]
508struct CompactNatsClaims {
509 #[serde(rename = "type")]
510 kind: Option<String>,
511}
512
513#[derive(Debug, Clone, PartialEq, Eq)]
515pub struct NatsJwtClaims {
516 pub issuer: String,
519 pub subject: String,
521 pub issued_at: Option<u64>,
523 pub expires_at: Option<u64>,
525 pub not_before: Option<u64>,
527 pub nats_type: Option<String>,
529 pub raw: Value,
532}
533
534#[derive(Debug, Clone, PartialEq, Eq)]
536pub struct DecodedNatsJwt {
537 signing_input: String,
538 signature: [u8; 64],
539 claims: NatsJwtClaims,
540 issuer_role: NkeyType,
541 issuer_public_key: [u8; 32],
542}
543
544impl DecodedNatsJwt {
545 #[must_use]
547 pub fn signing_input(&self) -> &str {
548 &self.signing_input
549 }
550
551 #[must_use]
553 pub const fn signature(&self) -> &[u8; 64] {
554 &self.signature
555 }
556
557 #[must_use]
559 pub const fn claims(&self) -> &NatsJwtClaims {
560 &self.claims
561 }
562
563 #[must_use]
565 pub const fn issuer_role(&self) -> NkeyType {
566 self.issuer_role
567 }
568
569 #[must_use]
571 pub const fn issuer_public_key(&self) -> &[u8; 32] {
572 &self.issuer_public_key
573 }
574
575 #[must_use]
577 pub fn verify_signature(&self, public_key: &[u8; 32]) -> bool {
578 verify_ed25519(public_key, self.signing_input.as_bytes(), &self.signature)
579 }
580
581 pub fn verify_with_candidates<'a, I>(
592 &self,
593 candidates: I,
594 now_unix: u64,
595 ) -> Result<NatsJwtValidation, Error>
596 where
597 I: IntoIterator<Item = CandidateSigner<'a>>,
598 {
599 for candidate in candidates {
600 let resolved = self.resolve_candidate(candidate)?;
601 if let Some(matched) = resolved {
602 return Ok(self.validation_for_match(matched, now_unix));
603 }
604 }
605 Ok(NatsJwtValidation {
606 reason: NatsJwtValidationReason::UnknownSigner,
607 matched_signer: None,
608 })
609 }
610
611 fn resolve_candidate(
612 &self,
613 candidate: CandidateSigner<'_>,
614 ) -> Result<Option<MatchedNatsSigner>, Error> {
615 match candidate {
616 CandidateSigner::Nkey(nkey) => {
617 let (role, public_key) = decode_public(nkey)?;
618 if role == self.issuer_role && public_key == self.issuer_public_key {
619 Ok(Some(MatchedNatsSigner {
620 role: Some(role),
621 public_key,
622 }))
623 } else {
624 Ok(None)
625 }
626 }
627 CandidateSigner::RawPublicKey(public_key) => {
628 if public_key == &self.issuer_public_key {
629 Ok(Some(MatchedNatsSigner {
630 role: None,
631 public_key: *public_key,
632 }))
633 } else {
634 Ok(None)
635 }
636 }
637 }
638 }
639
640 fn validation_for_match(
641 &self,
642 matched_signer: MatchedNatsSigner,
643 now_unix: u64,
644 ) -> NatsJwtValidation {
645 if !self.verify_signature(&matched_signer.public_key) {
646 return NatsJwtValidation {
647 reason: NatsJwtValidationReason::BadSignature,
648 matched_signer: None,
649 };
650 }
651 if let Some(exp) = self.claims.expires_at
652 && exp <= now_unix
653 {
654 return NatsJwtValidation {
655 reason: NatsJwtValidationReason::Expired,
656 matched_signer: None,
657 };
658 }
659 if let Some(nbf) = self.claims.not_before
660 && nbf > now_unix
661 {
662 return NatsJwtValidation {
663 reason: NatsJwtValidationReason::NotYetValid,
664 matched_signer: None,
665 };
666 }
667 NatsJwtValidation {
668 reason: NatsJwtValidationReason::Valid,
669 matched_signer: Some(matched_signer),
670 }
671 }
672}
673
674#[derive(Debug, Clone, Copy)]
676pub enum CandidateSigner<'a> {
677 Nkey(&'a str),
679 RawPublicKey(&'a [u8; 32]),
681}
682
683#[derive(Debug, Clone, Copy, PartialEq, Eq)]
685pub struct MatchedNatsSigner {
686 pub role: Option<NkeyType>,
689 pub public_key: [u8; 32],
691}
692
693#[derive(Debug, Clone, Copy, PartialEq, Eq)]
695pub struct NatsJwtValidation {
696 pub reason: NatsJwtValidationReason,
698 pub matched_signer: Option<MatchedNatsSigner>,
700}
701
702impl NatsJwtValidation {
703 #[must_use]
705 pub const fn is_valid(self) -> bool {
706 matches!(self.reason, NatsJwtValidationReason::Valid)
707 }
708}
709
710#[derive(Debug, Clone, Copy, PartialEq, Eq)]
712pub enum NatsJwtValidationReason {
713 Valid,
715 UnknownSigner,
717 BadSignature,
720 Expired,
722 NotYetValid,
724}
725
726pub fn decode_nats_jwt(token: &str) -> Result<DecodedNatsJwt, Error> {
735 let mut parts = token.split('.');
736 let Some(header_segment) = parts.next() else {
737 return Err(Error::MalformedJwt("jwt is empty".into()));
738 };
739 let Some(claims_segment) = parts.next() else {
740 return Err(Error::MalformedJwt(
741 "jwt must contain header, claims, and signature".into(),
742 ));
743 };
744 let Some(signature_segment) = parts.next() else {
745 return Err(Error::MalformedJwt(
746 "jwt must contain header, claims, and signature".into(),
747 ));
748 };
749 if parts.next().is_some() {
750 return Err(Error::MalformedJwt(
751 "jwt must contain exactly three segments".into(),
752 ));
753 }
754
755 let header: CompactHeader = decode_url_json(header_segment, "header")?;
756 if header.typ != "JWT" || header.alg != "ed25519-nkey" {
757 return Err(Error::MalformedJwt(
758 "jwt header must be typ=JWT and alg=ed25519-nkey".into(),
759 ));
760 }
761
762 let claims_raw = decode_url_bytes(claims_segment, "claims")?;
763 let claims_value: Value = serde_json::from_slice(&claims_raw)
764 .map_err(|e| Error::MalformedJwt(format!("invalid jwt claims json: {e}")))?;
765 let claims: CompactClaims = serde_json::from_value(claims_value.clone())
766 .map_err(|e| Error::MalformedJwt(format!("invalid jwt claims: {e}")))?;
767 let (issuer_role, issuer_public_key) = decode_public(&claims.iss)
768 .map_err(|e| Error::MalformedJwt(format!("invalid jwt issuer nkey: {e}")))?;
769
770 let signature = decode_url_bytes(signature_segment, "signature")?;
771 let signature_len = signature.len();
772 let Ok(signature) = <[u8; 64]>::try_from(signature) else {
773 return Err(Error::BadSignatureLen(signature_len));
774 };
775
776 Ok(DecodedNatsJwt {
777 signing_input: format!("{header_segment}.{claims_segment}"),
778 signature,
779 claims: NatsJwtClaims {
780 issuer: claims.iss,
781 subject: claims.sub,
782 issued_at: claims.iat,
783 expires_at: claims.exp,
784 not_before: claims.nbf,
785 nats_type: claims.nats.and_then(|nats| nats.kind),
786 raw: claims_value,
787 },
788 issuer_role,
789 issuer_public_key,
790 })
791}
792
793fn decode_url_json<T>(segment: &str, name: &str) -> Result<T, Error>
794where
795 T: DeserializeOwned,
796{
797 let bytes = decode_url_bytes(segment, name)?;
798 serde_json::from_slice(&bytes)
799 .map_err(|e| Error::MalformedJwt(format!("invalid jwt {name} json: {e}")))
800}
801
802fn decode_url_bytes(segment: &str, name: &str) -> Result<Vec<u8>, Error> {
803 URL_SAFE_NO_PAD
804 .decode(segment)
805 .map_err(|e| Error::MalformedJwt(format!("invalid jwt {name} base64url: {e}")))
806}
807
808fn verify_ed25519(public_key: &[u8; 32], message: &[u8], signature: &[u8; 64]) -> bool {
809 let signature = Signature::from_bytes(signature);
810 VerifyingKey::from_bytes(public_key).is_ok_and(|key| key.verify(message, &signature).is_ok())
811}
812
813#[derive(Serialize, Default, Clone)]
820pub struct Permission {
821 #[serde(skip_serializing_if = "Vec::is_empty")]
823 pub allow: Vec<String>,
824 #[serde(skip_serializing_if = "Vec::is_empty")]
826 pub deny: Vec<String>,
827}
828
829#[derive(Serialize)]
830struct NatsUser {
831 #[serde(rename = "pub")]
832 publish: Permission,
833 sub: Permission,
834 subs: i64,
835 data: i64,
836 payload: i64,
837 #[serde(skip_serializing_if = "Option::is_none")]
840 issuer_account: Option<String>,
841 #[serde(rename = "type")]
842 kind: &'static str,
843 version: i64,
844}
845
846#[derive(Serialize)]
850struct ClaimsHash<'a> {
851 #[serde(skip_serializing_if = "Option::is_none")]
852 aud: Option<&'a str>,
853 #[serde(skip_serializing_if = "Option::is_none")]
854 exp: Option<u64>,
855 #[serde(skip_serializing_if = "Option::is_none")]
856 jti: Option<&'a str>,
857 iat: u64,
858 iss: &'a str,
859 #[serde(skip_serializing_if = "Option::is_none")]
860 name: Option<&'a str>,
861 #[serde(skip_serializing_if = "Option::is_none")]
862 nbf: Option<u64>,
863 sub: &'a str,
864}
865
866#[derive(Serialize, Default, Clone)]
869pub struct Permissions {
870 #[serde(rename = "pub")]
872 pub publish: Permission,
873 pub sub: Permission,
875 #[serde(skip_serializing_if = "Option::is_none")]
877 pub resp: Option<ResponsePermission>,
878}
879
880#[derive(Serialize, Clone)]
883pub struct ResponsePermission {
884 pub max: i64,
886 pub ttl: i64,
888}
889
890#[derive(Serialize, Clone)]
892#[serde(rename_all = "lowercase")]
893pub enum ExportType {
894 Stream,
896 Service,
898}
899
900#[derive(Serialize, Clone)]
902pub struct AccountImport {
903 #[serde(skip_serializing_if = "str::is_empty")]
905 pub name: String,
906 #[serde(skip_serializing_if = "str::is_empty")]
908 pub subject: String,
909 #[serde(skip_serializing_if = "str::is_empty")]
911 pub account: String,
912 #[serde(skip_serializing_if = "str::is_empty")]
914 pub token: String,
915 #[serde(skip_serializing_if = "str::is_empty")]
917 pub to: String,
918 #[serde(skip_serializing_if = "str::is_empty")]
920 pub local_subject: String,
921 #[serde(rename = "type")]
923 pub kind: ExportType,
924 #[serde(skip_serializing_if = "is_false")]
926 pub share: bool,
927 #[serde(skip_serializing_if = "is_false")]
929 pub allow_trace: bool,
930}
931
932#[derive(Serialize, Clone)]
934pub struct ServiceLatency {
935 pub sampling: SamplingRate,
937 pub results: String,
939}
940
941#[derive(Clone)]
943pub enum SamplingRate {
944 Headers,
947 Percent(u8),
949}
950
951impl Serialize for SamplingRate {
952 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
953 where
954 S: serde::Serializer,
955 {
956 match self {
957 Self::Headers => serializer.serialize_str("headers"),
958 Self::Percent(percent) => serializer.serialize_u8(*percent),
959 }
960 }
961}
962
963#[derive(Serialize, Clone)]
966pub struct AccountExport {
967 #[serde(skip_serializing_if = "str::is_empty")]
969 pub name: String,
970 #[serde(skip_serializing_if = "str::is_empty")]
972 pub subject: String,
973 #[serde(rename = "type")]
975 pub kind: ExportType,
976 #[serde(skip_serializing_if = "is_false")]
978 pub token_req: bool,
979 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
981 pub revocations: BTreeMap<String, i64>,
982 #[serde(skip_serializing_if = "Option::is_none")]
984 pub response_type: Option<ResponseType>,
985 #[serde(skip_serializing_if = "is_zero_i64")]
987 pub response_threshold: i64,
988 #[serde(skip_serializing_if = "Option::is_none")]
990 pub service_latency: Option<ServiceLatency>,
991 #[serde(skip_serializing_if = "is_zero_u32")]
993 pub account_token_position: u32,
994 #[serde(skip_serializing_if = "is_false")]
996 pub advertise: bool,
997 #[serde(skip_serializing_if = "is_false")]
999 pub allow_trace: bool,
1000 #[serde(skip_serializing_if = "str::is_empty")]
1002 pub description: String,
1003 #[serde(skip_serializing_if = "str::is_empty")]
1005 pub info_url: String,
1006}
1007
1008#[derive(Serialize, Clone)]
1010pub enum ResponseType {
1011 Singleton,
1013 Stream,
1015 Chunked,
1017}
1018
1019#[derive(Serialize, Default, Clone)]
1021pub struct NatsLimits {
1022 #[serde(skip_serializing_if = "is_zero_i64")]
1024 pub subs: i64,
1025 #[serde(skip_serializing_if = "is_zero_i64")]
1027 pub data: i64,
1028 #[serde(skip_serializing_if = "is_zero_i64")]
1030 pub payload: i64,
1031}
1032
1033#[derive(Serialize, Default, Clone)]
1035pub struct AccountLimits {
1036 #[serde(skip_serializing_if = "is_zero_i64")]
1038 pub imports: i64,
1039 #[serde(skip_serializing_if = "is_zero_i64")]
1041 pub exports: i64,
1042 #[serde(skip_serializing_if = "is_false")]
1044 pub wildcards: bool,
1045 #[serde(skip_serializing_if = "is_false")]
1047 pub disallow_bearer: bool,
1048 #[serde(skip_serializing_if = "is_zero_i64")]
1050 pub conn: i64,
1051 #[serde(skip_serializing_if = "is_zero_i64")]
1053 pub leaf: i64,
1054}
1055
1056#[derive(Serialize, Default, Clone)]
1059pub struct JetStreamLimits {
1060 #[serde(skip_serializing_if = "is_zero_i64")]
1062 pub mem_storage: i64,
1063 #[serde(skip_serializing_if = "is_zero_i64")]
1065 pub disk_storage: i64,
1066 #[serde(skip_serializing_if = "is_zero_i64")]
1068 pub streams: i64,
1069 #[serde(skip_serializing_if = "is_zero_i64")]
1071 pub consumer: i64,
1072 #[serde(skip_serializing_if = "is_zero_i64")]
1074 pub max_ack_pending: i64,
1075 #[serde(skip_serializing_if = "is_zero_i64")]
1077 pub mem_max_stream_bytes: i64,
1078 #[serde(skip_serializing_if = "is_zero_i64")]
1080 pub disk_max_stream_bytes: i64,
1081 #[serde(skip_serializing_if = "is_false")]
1083 pub max_bytes_required: bool,
1084}
1085
1086#[derive(Serialize, Default, Clone)]
1090pub struct OperatorLimits {
1091 #[serde(flatten)]
1093 pub nats: NatsLimits,
1094 #[serde(flatten)]
1096 pub account: AccountLimits,
1097 #[serde(flatten)]
1099 pub jetstream: JetStreamLimits,
1100 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
1102 pub tiered_limits: BTreeMap<String, JetStreamLimits>,
1103}
1104
1105impl OperatorLimits {
1106 #[must_use]
1116 pub fn unlimited() -> Self {
1117 Self {
1118 nats: NatsLimits {
1119 subs: -1,
1120 data: -1,
1121 payload: -1,
1122 },
1123 account: AccountLimits {
1124 imports: -1,
1125 exports: -1,
1126 wildcards: true,
1127 conn: -1,
1128 leaf: -1,
1129 ..AccountLimits::default()
1130 },
1131 ..Self::default()
1132 }
1133 }
1134}
1135
1136#[derive(Serialize, Clone)]
1138pub struct WeightedMapping {
1139 pub subject: String,
1141 #[serde(skip_serializing_if = "is_zero_u8")]
1143 pub weight: u8,
1144 #[serde(skip_serializing_if = "str::is_empty")]
1146 pub cluster: String,
1147}
1148
1149#[derive(Serialize, Default, Clone)]
1152pub struct ExternalAuthorization {
1153 #[serde(skip_serializing_if = "Vec::is_empty")]
1155 pub auth_users: Vec<String>,
1156 #[serde(skip_serializing_if = "Vec::is_empty")]
1158 pub allowed_accounts: Vec<String>,
1159 #[serde(skip_serializing_if = "str::is_empty")]
1161 pub xkey: String,
1162}
1163
1164#[derive(Serialize, Clone)]
1167pub struct MsgTrace {
1168 #[serde(skip_serializing_if = "str::is_empty")]
1170 pub dest: String,
1171 #[serde(skip_serializing_if = "is_zero_u8")]
1173 pub sampling: u8,
1174}
1175
1176#[derive(Serialize, Clone)]
1178#[serde(rename_all = "lowercase")]
1179pub enum ClusterTraffic {
1180 System,
1182 Owner,
1184}
1185
1186#[derive(Serialize, Default, Clone)]
1190pub struct AccountClaims {
1191 #[serde(skip_serializing_if = "Vec::is_empty")]
1193 pub imports: Vec<AccountImport>,
1194 #[serde(skip_serializing_if = "Vec::is_empty")]
1196 pub exports: Vec<AccountExport>,
1197 #[serde(skip_serializing_if = "operator_limits_is_empty")]
1199 pub limits: OperatorLimits,
1200 #[serde(rename = "signing_keys", skip_serializing_if = "Vec::is_empty")]
1203 pub signing_keys: Vec<String>,
1204 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
1206 pub revocations: BTreeMap<String, i64>,
1207 #[serde(skip_serializing_if = "Option::is_none")]
1209 pub default_permissions: Option<Permissions>,
1210 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
1212 pub mappings: BTreeMap<String, Vec<WeightedMapping>>,
1213 #[serde(skip_serializing_if = "external_authorization_is_empty")]
1215 pub authorization: ExternalAuthorization,
1216 #[serde(skip_serializing_if = "Option::is_none")]
1218 pub trace: Option<MsgTrace>,
1219 #[serde(skip_serializing_if = "Option::is_none")]
1221 pub cluster_traffic: Option<ClusterTraffic>,
1222}
1223
1224#[derive(Serialize)]
1225struct NatsAccount {
1226 #[serde(flatten)]
1227 claims: AccountClaims,
1228 #[serde(rename = "type")]
1229 kind: &'static str,
1230 version: i64,
1231}
1232
1233#[derive(Serialize, Default, Clone)]
1236pub struct OperatorClaims {
1237 #[serde(rename = "signing_keys", skip_serializing_if = "Vec::is_empty")]
1240 pub signing_keys: Vec<String>,
1241 #[serde(rename = "account_server_url", skip_serializing_if = "str::is_empty")]
1244 pub account_server_url: String,
1245 #[serde(skip_serializing_if = "Vec::is_empty")]
1247 pub operator_service_urls: Vec<String>,
1248 #[serde(rename = "system_account", skip_serializing_if = "str::is_empty")]
1251 pub system_account: String,
1252 #[serde(skip_serializing_if = "str::is_empty")]
1254 pub assert_server_version: String,
1255 #[serde(skip_serializing_if = "is_false")]
1258 pub strict_signing_key_usage: bool,
1259}
1260
1261#[derive(Serialize)]
1262struct NatsOperator {
1263 #[serde(flatten)]
1264 claims: OperatorClaims,
1265 #[serde(rename = "type")]
1266 kind: &'static str,
1267 version: i64,
1268}
1269
1270#[allow(clippy::trivially_copy_pass_by_ref)]
1271const fn is_false(value: &bool) -> bool {
1272 !*value
1273}
1274
1275#[allow(clippy::trivially_copy_pass_by_ref)]
1276const fn is_zero_i64(value: &i64) -> bool {
1277 *value == 0
1278}
1279
1280#[allow(clippy::trivially_copy_pass_by_ref)]
1281const fn is_zero_u32(value: &u32) -> bool {
1282 *value == 0
1283}
1284
1285#[allow(clippy::trivially_copy_pass_by_ref)]
1286const fn is_zero_u8(value: &u8) -> bool {
1287 *value == 0
1288}
1289
1290fn external_authorization_is_empty(value: &ExternalAuthorization) -> bool {
1291 value.auth_users.is_empty() && value.allowed_accounts.is_empty() && value.xkey.is_empty()
1292}
1293
1294fn operator_limits_is_empty(value: &OperatorLimits) -> bool {
1295 value.nats.subs == 0
1296 && value.nats.data == 0
1297 && value.nats.payload == 0
1298 && value.account.imports == 0
1299 && value.account.exports == 0
1300 && !value.account.wildcards
1301 && !value.account.disallow_bearer
1302 && value.account.conn == 0
1303 && value.account.leaf == 0
1304 && value.jetstream.mem_storage == 0
1305 && value.jetstream.disk_storage == 0
1306 && value.jetstream.streams == 0
1307 && value.jetstream.consumer == 0
1308 && value.jetstream.max_ack_pending == 0
1309 && value.jetstream.mem_max_stream_bytes == 0
1310 && value.jetstream.disk_max_stream_bytes == 0
1311 && !value.jetstream.max_bytes_required
1312 && value.tiered_limits.is_empty()
1313}
1314
1315#[derive(Serialize)]
1318struct NatsRole {
1319 #[serde(rename = "type")]
1320 kind: &'static str,
1321 version: i64,
1322}
1323
1324#[derive(Serialize)]
1328struct TokenClaimsGeneric<'a, N>
1329where
1330 N: Serialize,
1331{
1332 jti: String,
1333 iat: u64,
1334 iss: &'a str,
1335 #[serde(skip_serializing_if = "str::is_empty")]
1336 name: &'a str,
1337 #[serde(skip_serializing_if = "Option::is_none")]
1338 exp: Option<u64>,
1339 #[serde(skip_serializing_if = "Option::is_none")]
1340 nbf: Option<u64>,
1341 nats: N,
1342 sub: &'a str,
1343}
1344
1345fn build_signing_input<N: Serialize>(
1350 iss: &str,
1351 sub: &str,
1352 name: &str,
1353 iat: u64,
1354 exp: Option<u64>,
1355 nats: N,
1356) -> Result<String, Error> {
1357 let jti = jti_for_standard_claims(iss, sub, Some(name), iat, exp, None, None)?;
1358
1359 let claims = TokenClaimsGeneric {
1360 jti,
1361 iat,
1362 iss,
1363 name,
1364 exp,
1365 nbf: None,
1366 nats,
1367 sub,
1368 };
1369 let header = URL_SAFE_NO_PAD.encode(HEADER_JSON.as_bytes());
1370 let payload = URL_SAFE_NO_PAD.encode(serde_json::to_vec(&claims)?);
1371 Ok(format!("{header}.{payload}"))
1372}
1373
1374pub fn jti_for_standard_claims(
1384 iss: &str,
1385 sub: &str,
1386 name: Option<&str>,
1387 iat: u64,
1388 exp: Option<u64>,
1389 aud: Option<&str>,
1390 nbf: Option<u64>,
1391) -> Result<String, Error> {
1392 let hash_src = ClaimsHash {
1393 aud: aud.filter(|value| !value.is_empty()),
1394 exp: exp.filter(|value| *value != 0),
1395 jti: None,
1396 iat,
1397 iss,
1398 name: name.filter(|value| !value.is_empty()),
1399 nbf: nbf.filter(|value| *value != 0),
1400 sub,
1401 };
1402 let mut hasher = Sha512_256::new();
1403 hasher.update(serde_json::to_vec(&hash_src)?);
1404 Ok(data_encoding::BASE32_NOPAD.encode(&hasher.finalize()))
1405}
1406
1407pub fn signing_input_from_claims(claims: &Value) -> Result<String, Error> {
1418 if !claims.is_object() {
1419 return Err(Error::InvalidClaims(
1420 "nats jwt claims must be a JSON object".into(),
1421 ));
1422 }
1423 let header = URL_SAFE_NO_PAD.encode(HEADER_JSON.as_bytes());
1424 let payload = URL_SAFE_NO_PAD.encode(serde_json::to_vec(claims)?);
1425 Ok(format!("{header}.{payload}"))
1426}
1427
1428#[derive(Default, Clone)]
1435pub struct UserPermissions {
1436 pub pub_allow: Vec<String>,
1438 pub pub_deny: Vec<String>,
1440 pub sub_allow: Vec<String>,
1442 pub sub_deny: Vec<String>,
1444}
1445
1446pub struct UserJwt {
1448 pub issuer: String,
1451 pub issuer_account: Option<String>,
1456 pub subject_user: String,
1458 pub name: String,
1460 pub issued_at: u64,
1462 pub expires: Option<u64>,
1464 pub permissions: UserPermissions,
1466}
1467
1468impl UserJwt {
1469 pub fn signing_input(&self) -> Result<String, Error> {
1473 let nats = NatsUser {
1474 publish: Permission {
1475 allow: self.permissions.pub_allow.clone(),
1476 deny: self.permissions.pub_deny.clone(),
1477 },
1478 sub: Permission {
1479 allow: self.permissions.sub_allow.clone(),
1480 deny: self.permissions.sub_deny.clone(),
1481 },
1482 subs: -1,
1483 data: -1,
1484 payload: -1,
1485 issuer_account: self.issuer_account.clone(),
1486 kind: "user",
1487 version: 2,
1488 };
1489 build_signing_input(
1490 &self.issuer,
1491 &self.subject_user,
1492 &self.name,
1493 self.issued_at,
1494 self.expires,
1495 nats,
1496 )
1497 }
1498}
1499
1500pub struct AccountJwt {
1510 pub issuer: String,
1512 pub subject_account: String,
1514 pub name: String,
1516 pub issued_at: u64,
1518 pub expires: Option<u64>,
1520 pub signing_keys: Vec<String>,
1523 pub claims: AccountClaims,
1525}
1526
1527impl AccountJwt {
1528 pub fn signing_input(&self) -> Result<String, Error> {
1535 let nats = NatsAccount {
1536 claims: {
1537 let mut claims = self.claims.clone();
1538 if claims.signing_keys.is_empty() {
1539 claims.signing_keys.clone_from(&self.signing_keys);
1540 }
1541 claims
1542 },
1543 kind: "account",
1544 version: 2,
1545 };
1546 build_signing_input(
1547 &self.issuer,
1548 &self.subject_account,
1549 &self.name,
1550 self.issued_at,
1551 self.expires,
1552 nats,
1553 )
1554 }
1555}
1556
1557pub struct OperatorJwt {
1565 pub issuer: String,
1567 pub subject_operator: String,
1570 pub name: String,
1572 pub issued_at: u64,
1574 pub expires: Option<u64>,
1576 pub signing_keys: Vec<String>,
1579 pub account_server_url: String,
1582 pub system_account: String,
1585 pub claims: OperatorClaims,
1587}
1588
1589impl OperatorJwt {
1590 pub fn signing_input(&self) -> Result<String, Error> {
1596 let nats = NatsOperator {
1597 claims: {
1598 let mut claims = self.claims.clone();
1599 if claims.signing_keys.is_empty() {
1600 claims.signing_keys.clone_from(&self.signing_keys);
1601 }
1602 if claims.account_server_url.is_empty() {
1603 claims
1604 .account_server_url
1605 .clone_from(&self.account_server_url);
1606 }
1607 if claims.system_account.is_empty() {
1608 claims.system_account.clone_from(&self.system_account);
1609 }
1610 claims
1611 },
1612 kind: "operator",
1613 version: 2,
1614 };
1615 build_signing_input(
1616 &self.issuer,
1617 &self.subject_operator,
1618 &self.name,
1619 self.issued_at,
1620 self.expires,
1621 nats,
1622 )
1623 }
1624}
1625
1626#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1634pub enum RoleKind {
1635 Server,
1637 Curve,
1639 Signer,
1641}
1642
1643impl RoleKind {
1644 #[must_use]
1646 pub const fn as_str(self) -> &'static str {
1647 match self {
1648 Self::Server => "server",
1649 Self::Curve => "curve",
1650 Self::Signer => "signer",
1651 }
1652 }
1653}
1654
1655impl core::fmt::Display for RoleKind {
1656 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1657 f.write_str(self.as_str())
1658 }
1659}
1660
1661pub struct RoleJwt {
1665 pub issuer: String,
1667 pub subject: String,
1669 pub name: String,
1671 pub issued_at: u64,
1673 pub expires: Option<u64>,
1675 pub kind: RoleKind,
1677}
1678
1679impl RoleJwt {
1680 pub fn signing_input(&self) -> Result<String, Error> {
1686 let nats = NatsRole {
1687 kind: self.kind.as_str(),
1688 version: 2,
1689 };
1690 build_signing_input(
1691 &self.issuer,
1692 &self.subject,
1693 &self.name,
1694 self.issued_at,
1695 self.expires,
1696 nats,
1697 )
1698 }
1699}
1700
1701#[must_use]
1704pub fn assemble(signing_input: &str, signature: &[u8]) -> String {
1705 format!("{signing_input}.{}", URL_SAFE_NO_PAD.encode(signature))
1706}
1707
1708pub fn format_user_creds(jwt: &str, seed: &str) -> Result<Zeroizing<String>, Error> {
1720 let jwt = single_line_creds_field("NATS user JWT", jwt)?;
1721 let seed = single_line_creds_field("NATS user NKey seed", seed)?;
1722 Ok(Zeroizing::new(format!(
1723 "-----BEGIN NATS USER JWT-----\n{jwt}\n------END NATS USER JWT------\n\n************************* IMPORTANT *************************\nNKEY Seed printed below can be used to sign and prove identity.\nNKEYs are sensitive and should be treated as secrets.\n\n-----BEGIN USER NKEY SEED-----\n{seed}\n------END USER NKEY SEED------\n\n*************************************************************\n"
1724 )))
1725}
1726
1727fn single_line_creds_field<'a>(name: &str, value: &'a str) -> Result<&'a str, Error> {
1728 let trimmed = value.trim();
1729 if trimmed.is_empty() {
1730 return Err(Error::InvalidClaims(format!("{name} must not be empty")));
1731 }
1732 if trimmed.contains('\n') || trimmed.contains('\r') {
1733 return Err(Error::InvalidClaims(format!(
1734 "{name} must be a single line"
1735 )));
1736 }
1737 Ok(trimmed)
1738}
1739
1740#[cfg(test)]
1741mod tests {
1742 use super::*;
1743 use alloc::string::ToString;
1744 use alloc::vec;
1745 use nkeys::{KeyPair, XKey};
1746 use serde_json::json;
1747
1748 #[derive(Default)]
1749 struct TestRng(u64);
1750
1751 impl rand_core::TryRng for TestRng {
1752 type Error = core::convert::Infallible;
1753
1754 fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
1755 let mut bytes = [0u8; 4];
1756 self.try_fill_bytes(&mut bytes)?;
1757 Ok(u32::from_le_bytes(bytes))
1758 }
1759
1760 fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
1761 let mut bytes = [0u8; 8];
1762 self.try_fill_bytes(&mut bytes)?;
1763 Ok(u64::from_le_bytes(bytes))
1764 }
1765
1766 fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> {
1767 for byte in dst {
1768 *byte = self.0.to_le_bytes()[0].wrapping_mul(37).wrapping_add(0x5a);
1769 self.0 = self.0.wrapping_add(1);
1770 }
1771 Ok(())
1772 }
1773 }
1774
1775 fn signed_token(signing_input: &str, signer: &KeyPair) -> String {
1776 let signature = signer.sign(signing_input.as_bytes()).expect("sign");
1777 assemble(signing_input, &signature)
1778 }
1779
1780 #[test]
1781 fn format_user_creds_renders_canonical_nsc_document() {
1782 let creds = format_user_creds(" jwt.token.sig ", " SUUSERSEED ")
1783 .expect("credentials document must render");
1784 assert_eq!(
1785 creds.as_str(),
1786 "-----BEGIN NATS USER JWT-----\njwt.token.sig\n------END NATS USER JWT------\n\n************************* IMPORTANT *************************\nNKEY Seed printed below can be used to sign and prove identity.\nNKEYs are sensitive and should be treated as secrets.\n\n-----BEGIN USER NKEY SEED-----\nSUUSERSEED\n------END USER NKEY SEED------\n\n*************************************************************\n"
1787 );
1788 }
1789
1790 #[test]
1791 fn format_user_creds_rejects_empty_or_multiline_fields() {
1792 assert!(format_user_creds("", "SUUSERSEED").is_err());
1793 assert!(format_user_creds("jwt.token.sig", "").is_err());
1794 assert!(format_user_creds("jwt\nsecond", "SUUSERSEED").is_err());
1795 assert!(format_user_creds("jwt.token.sig", "SU\nseed").is_err());
1796 }
1797
1798 fn assert_valid_token(token: &str, signer: &KeyPair, expected_kind: &str) {
1799 let signer_public = signer.public_key();
1800 let decoded = decode_nats_jwt(token).expect("decode jwt");
1801 assert_eq!(decoded.claims().issuer, signer_public);
1802 assert_eq!(decoded.claims().nats_type.as_deref(), Some(expected_kind));
1803 assert!(decoded.verify_signature(decoded.issuer_public_key()));
1804
1805 let validation = decoded
1806 .verify_with_candidates(
1807 [CandidateSigner::Nkey(&signer_public)],
1808 decoded.claims().issued_at.unwrap_or_default(),
1809 )
1810 .expect("validate");
1811 assert_eq!(validation.reason, NatsJwtValidationReason::Valid);
1812 assert!(validation.is_valid());
1813 assert!(validation.matched_signer.is_some());
1814
1815 let raw_validation = decoded
1816 .verify_with_candidates(
1817 [CandidateSigner::RawPublicKey(decoded.issuer_public_key())],
1818 decoded.claims().issued_at.unwrap_or_default(),
1819 )
1820 .expect("validate raw");
1821 assert_eq!(raw_validation.reason, NatsJwtValidationReason::Valid);
1822 assert!(raw_validation.is_valid());
1823 assert!(raw_validation.matched_signer.is_some());
1824 }
1825
1826 #[test]
1827 fn account_nkey_encoding_matches_nkeys() {
1828 let account = KeyPair::new_account();
1831 let expected = account.public_key(); let (role, raw) = decode_public(&expected).expect("decode");
1833 assert_eq!(role, NkeyType::Account);
1834 assert_eq!(encode_public(NkeyType::Account, &raw).unwrap(), expected);
1835 }
1836
1837 #[test]
1838 fn decode_public_reports_bad_encoding_separately_from_bad_length() {
1839 assert!(matches!(
1840 decode_public("not-an-nkey"),
1841 Err(Error::BadEncoding)
1842 ));
1843 let short = data_encoding::BASE32_NOPAD.encode(&[0]);
1844 assert!(matches!(
1845 decode_public(&short),
1846 Err(Error::BadPublicKeyLen(_))
1847 ));
1848 }
1849
1850 #[test]
1851 fn user_jwt_verifies_under_issuer_account_key() {
1852 let account = KeyPair::new_account();
1856 let user = KeyPair::new_user();
1857
1858 let jwt = UserJwt {
1859 issuer: account.public_key(),
1860 issuer_account: None,
1861 subject_user: user.public_key(),
1862 name: "bob".into(),
1863 issued_at: 1_782_000_000,
1864 expires: None,
1865 permissions: UserPermissions::default(),
1866 };
1867
1868 let signing_input = jwt.signing_input().expect("signing input");
1869 let signature = account.sign(signing_input.as_bytes()).expect("sign");
1870 let token = assemble(&signing_input, &signature);
1871
1872 let parts: Vec<&str> = token.split('.').collect();
1874 assert_eq!(parts.len(), 3);
1875
1876 let header = URL_SAFE_NO_PAD.decode(parts[0]).unwrap();
1878 assert_eq!(header, HEADER_JSON.as_bytes());
1879
1880 let claims: serde_json::Value =
1882 serde_json::from_slice(&URL_SAFE_NO_PAD.decode(parts[1]).unwrap()).unwrap();
1883 assert_eq!(claims["iss"], account.public_key());
1884 assert_eq!(claims["sub"], user.public_key());
1885 assert_eq!(claims["nats"]["type"], "user");
1886 assert_eq!(claims["nats"]["version"], 2);
1887 assert!(claims["jti"].as_str().unwrap().len() >= 52);
1888
1889 let issuer = KeyPair::from_public_key(claims["iss"].as_str().unwrap()).unwrap();
1892 issuer
1893 .verify(signing_input.as_bytes(), &signature)
1894 .expect("signature must verify under iss account key");
1895 }
1896
1897 #[test]
1898 fn user_jwt_sets_nats_issuer_account_when_signing_key() {
1899 let signing = KeyPair::new_account();
1903 let account_identity = KeyPair::new_account();
1904 let user = KeyPair::new_user();
1905
1906 let jwt = UserJwt {
1907 issuer: signing.public_key(),
1908 issuer_account: Some(account_identity.public_key()),
1909 subject_user: user.public_key(),
1910 name: "svc".into(),
1911 issued_at: 1_782_000_000,
1912 expires: None,
1913 permissions: UserPermissions::default(),
1914 };
1915 let signing_input = jwt.signing_input().expect("signing input");
1916 let parts: Vec<&str> = signing_input.split('.').collect();
1917 assert_eq!(parts.len(), 2);
1918 let claims: serde_json::Value =
1919 serde_json::from_slice(&URL_SAFE_NO_PAD.decode(parts[1]).unwrap()).unwrap();
1920 assert_eq!(claims["iss"], signing.public_key());
1921 assert_eq!(
1922 claims["nats"]["issuer_account"],
1923 account_identity.public_key()
1924 );
1925
1926 let plain = UserJwt {
1928 issuer: signing.public_key(),
1929 issuer_account: None,
1930 subject_user: user.public_key(),
1931 name: "svc".into(),
1932 issued_at: 1_782_000_000,
1933 expires: None,
1934 permissions: UserPermissions::default(),
1935 };
1936 let plain_input = plain.signing_input().expect("signing input");
1937 let plain_parts: Vec<&str> = plain_input.split('.').collect();
1938 let plain_claims: serde_json::Value =
1939 serde_json::from_slice(&URL_SAFE_NO_PAD.decode(plain_parts[1]).unwrap()).unwrap();
1940 assert!(plain_claims["nats"].get("issuer_account").is_none());
1941 }
1942
1943 #[test]
1944 fn encode_public_round_trips_each_role() {
1945 let account = KeyPair::new_account();
1948 let (_, raw) = decode_public(&account.public_key()).unwrap();
1949 for role in [
1950 NkeyType::Account,
1951 NkeyType::Cluster,
1952 NkeyType::Server,
1953 NkeyType::Operator,
1954 NkeyType::User,
1955 NkeyType::Curve,
1956 ] {
1957 let encoded = encode_public(role, &raw).unwrap();
1958 assert!(
1959 encoded.starts_with(role.letter()),
1960 "{role} key should start with {}, got {encoded}",
1961 role.letter()
1962 );
1963 let (decoded_role, decoded_raw) = decode_public(&encoded).unwrap();
1964 assert_eq!(decoded_role, role);
1965 assert_eq!(decoded_raw, raw);
1966 }
1967 }
1968
1969 #[test]
1970 fn nats_curve_box_round_trips_and_validates_wire_shape() {
1971 let sender_private = Zeroizing::new([0x11; 32]);
1972 let receiver_private = Zeroizing::new([0x22; 32]);
1973 let sender_public =
1974 encode_public(NkeyType::Curve, &xkey_public_from_private(&sender_private))
1975 .expect("sender xkey encodes");
1976 let receiver_public = encode_public(
1977 NkeyType::Curve,
1978 &xkey_public_from_private(&receiver_private),
1979 )
1980 .expect("receiver xkey encodes");
1981
1982 let mut rng = TestRng::default();
1983 let boxed = seal_nats_curve(&sender_private, &receiver_public, b"payload", &mut rng)
1984 .expect("seal succeeds");
1985 assert!(boxed.starts_with(XKEY_VERSION_V1));
1986 assert_eq!(
1987 boxed.len(),
1988 XKEY_VERSION_V1.len() + XKEY_NONCE_LEN + XKEY_TAG_LEN + b"payload".len()
1989 );
1990
1991 let opened =
1992 open_nats_curve(&receiver_private, &sender_public, &boxed).expect("open succeeds");
1993 assert_eq!(opened.as_slice(), b"payload");
1994
1995 let mut tampered = boxed;
1996 if let Some(byte) = tampered.last_mut() {
1997 *byte ^= 0x80;
1998 }
1999 assert!(matches!(
2000 open_nats_curve(&receiver_private, &sender_public, &tampered),
2001 Err(Error::XKeyOpenFailed)
2002 ));
2003 }
2004
2005 #[test]
2006 fn nats_curve_box_empty_payload_round_trips() {
2007 let sender_private = Zeroizing::new([0x55; 32]);
2008 let receiver_private = Zeroizing::new([0x66; 32]);
2009 let sender_public =
2010 encode_public(NkeyType::Curve, &xkey_public_from_private(&sender_private))
2011 .expect("sender xkey encodes");
2012 let receiver_public = encode_public(
2013 NkeyType::Curve,
2014 &xkey_public_from_private(&receiver_private),
2015 )
2016 .expect("receiver xkey encodes");
2017
2018 let mut rng = TestRng::default();
2019 let boxed = seal_nats_curve(&sender_private, &receiver_public, b"", &mut rng)
2020 .expect("seal of empty payload succeeds");
2021 assert_eq!(
2022 boxed.len(),
2023 XKEY_VERSION_V1.len() + XKEY_NONCE_LEN + XKEY_TAG_LEN
2024 );
2025
2026 let opened = open_nats_curve(&receiver_private, &sender_public, &boxed)
2027 .expect("open of authentic empty box succeeds");
2028 assert!(opened.is_empty());
2029
2030 let (_, truncated) = boxed.split_last().expect("box is non-empty");
2032 assert!(matches!(
2033 open_nats_curve(&receiver_private, &sender_public, truncated),
2034 Err(Error::BadXKeyCiphertextLen(_))
2035 ));
2036
2037 let sender = XKey::new_from_raw(*sender_private);
2039 let receiver = XKey::new_from_raw(*receiver_private);
2040 let nkeys_opened = receiver
2041 .open(&boxed, &sender)
2042 .expect("nkeys opens basil empty box");
2043 assert!(nkeys_opened.is_empty());
2044 let nkeys_box = sender
2045 .seal(b"", &receiver)
2046 .expect("nkeys seals empty payload");
2047 let basil_opened = open_nats_curve(&receiver_private, &sender.public_key(), &nkeys_box)
2048 .expect("basil opens nkeys empty box");
2049 assert!(basil_opened.is_empty());
2050 }
2051
2052 #[test]
2053 fn nats_curve_box_interops_with_nkeys_xkey_both_directions() {
2054 let sender_private = Zeroizing::new([0x33; 32]);
2055 let receiver_private = Zeroizing::new([0x44; 32]);
2056 let sender = XKey::new_from_raw(*sender_private);
2057 let receiver = XKey::new_from_raw(*receiver_private);
2058
2059 let mut rng = TestRng::default();
2060 let basil_box = seal_nats_curve(
2061 &sender_private,
2062 &receiver.public_key(),
2063 b"from basil",
2064 &mut rng,
2065 )
2066 .expect("basil seal succeeds");
2067 let nkeys_opened = receiver
2068 .open(&basil_box, &sender)
2069 .expect("nkeys opens basil box");
2070 assert_eq!(nkeys_opened, b"from basil");
2071
2072 let nkeys_box = sender
2073 .seal(b"from nkeys", &receiver)
2074 .expect("nkeys seal succeeds");
2075 let basil_opened = open_nats_curve(&receiver_private, &sender.public_key(), &nkeys_box)
2076 .expect("basil opens nkeys box");
2077 assert_eq!(basil_opened.as_slice(), b"from nkeys");
2078 }
2079
2080 #[test]
2081 fn nkey_type_letter_round_trips() {
2082 for role in NkeyType::ALL {
2083 assert_eq!(NkeyType::from_letter(role.letter()), Some(role));
2084 }
2085 assert_eq!(NkeyType::from_letter('Z'), None);
2087 assert_eq!(NkeyType::from_letter('S'), None);
2088 }
2089
2090 #[test]
2091 fn require_public_prefix_rejects_wrong_role() {
2092 let user = KeyPair::new_user();
2093 let err =
2094 require_public_prefix(&user.public_key(), NkeyType::Account).expect_err("wrong role");
2095 assert!(matches!(
2096 err,
2097 Error::UnexpectedPrefix {
2098 expected: NkeyType::Account,
2099 actual: NkeyType::User
2100 }
2101 ));
2102 require_public_prefix(&user.public_key(), NkeyType::User).expect("user role");
2103 }
2104
2105 #[test]
2106 fn account_jwt_verifies_under_issuer_operator_key() {
2107 let operator = KeyPair::new_operator();
2110 let account = KeyPair::new_account();
2111 let signing = KeyPair::new_account();
2112
2113 let jwt = AccountJwt {
2114 issuer: operator.public_key(),
2115 subject_account: account.public_key(),
2116 name: "acme".into(),
2117 issued_at: 1_782_000_000,
2118 expires: Some(1_782_003_600),
2119 signing_keys: vec![signing.public_key()],
2120 claims: AccountClaims::default(),
2121 };
2122
2123 let signing_input = jwt.signing_input().expect("signing input");
2124 let signature = operator.sign(signing_input.as_bytes()).expect("sign");
2125 let token = assemble(&signing_input, &signature);
2126
2127 let parts: Vec<&str> = token.split('.').collect();
2128 assert_eq!(parts.len(), 3);
2129 assert_eq!(
2130 URL_SAFE_NO_PAD.decode(parts[0]).unwrap(),
2131 HEADER_JSON.as_bytes()
2132 );
2133 let claims: serde_json::Value =
2134 serde_json::from_slice(&URL_SAFE_NO_PAD.decode(parts[1]).unwrap()).unwrap();
2135 assert_eq!(claims["iss"], operator.public_key());
2136 assert_eq!(claims["sub"], account.public_key());
2137 assert_eq!(claims["nats"]["type"], "account");
2138 assert_eq!(claims["nats"]["version"], 2);
2139 assert_eq!(claims["nats"]["signing_keys"][0], signing.public_key());
2140 assert_eq!(claims["exp"], 1_782_003_600u64);
2141
2142 let issuer = KeyPair::from_public_key(claims["iss"].as_str().unwrap()).unwrap();
2143 issuer
2144 .verify(signing_input.as_bytes(), &signature)
2145 .expect("must verify under iss operator key");
2146 }
2147
2148 #[test]
2149 fn operator_jwt_self_signed_verifies_and_omits_empty_fields() {
2150 let operator = KeyPair::new_operator();
2153 let sys = KeyPair::new_account();
2154
2155 let jwt = OperatorJwt {
2156 issuer: operator.public_key(),
2157 subject_operator: operator.public_key(),
2158 name: "root-op".into(),
2159 issued_at: 1_782_000_000,
2160 expires: None,
2161 signing_keys: Vec::new(),
2162 account_server_url: "nats://localhost:4222".into(),
2163 system_account: sys.public_key(),
2164 claims: OperatorClaims::default(),
2165 };
2166
2167 let signing_input = jwt.signing_input().expect("signing input");
2168 let signature = operator.sign(signing_input.as_bytes()).expect("sign");
2169 let token = assemble(&signing_input, &signature);
2170
2171 let parts: Vec<&str> = token.split('.').collect();
2172 let claims: serde_json::Value =
2173 serde_json::from_slice(&URL_SAFE_NO_PAD.decode(parts[1]).unwrap()).unwrap();
2174 assert_eq!(claims["iss"], operator.public_key());
2175 assert_eq!(claims["sub"], operator.public_key());
2176 assert_eq!(claims["nats"]["type"], "operator");
2177 assert_eq!(
2178 claims["nats"]["account_server_url"],
2179 "nats://localhost:4222"
2180 );
2181 assert_eq!(claims["nats"]["system_account"], sys.public_key());
2182 assert!(claims.get("exp").is_none());
2184 assert!(claims["nats"].get("signing_keys").is_none());
2186
2187 let issuer = KeyPair::from_public_key(claims["iss"].as_str().unwrap()).unwrap();
2188 issuer
2189 .verify(signing_input.as_bytes(), &signature)
2190 .expect("must verify under iss operator key");
2191 }
2192
2193 #[test]
2194 #[allow(clippy::too_many_lines)]
2195 fn account_jwt_serializes_rich_claim_surface() {
2196 let operator = KeyPair::new_operator();
2197 let account = KeyPair::new_account();
2198 let signing = KeyPair::new_account();
2199 let exporting = KeyPair::new_account();
2200 let user = KeyPair::new_user();
2201
2202 let mut revocations = BTreeMap::new();
2203 revocations.insert(user.public_key(), 1_782_000_001);
2204 let mut export_revocations = BTreeMap::new();
2205 export_revocations.insert("*".to_string(), 1_782_000_002);
2206 let mut tiered_limits = BTreeMap::new();
2207 tiered_limits.insert(
2208 "gold".to_string(),
2209 JetStreamLimits {
2210 mem_storage: 64,
2211 disk_storage: 128,
2212 streams: 3,
2213 consumer: 4,
2214 max_ack_pending: 5,
2215 mem_max_stream_bytes: 6,
2216 disk_max_stream_bytes: 7,
2217 max_bytes_required: true,
2218 },
2219 );
2220
2221 let jwt = AccountJwt {
2222 issuer: operator.public_key(),
2223 subject_account: account.public_key(),
2224 name: "tenant".into(),
2225 issued_at: 1_782_000_000,
2226 expires: None,
2227 signing_keys: vec![signing.public_key()],
2228 claims: AccountClaims {
2229 imports: vec![AccountImport {
2230 name: "orders-in".into(),
2231 subject: "orders.*".into(),
2232 account: exporting.public_key(),
2233 token: "activation.jwt".into(),
2236 to: String::new(),
2237 local_subject: "tenant.$1".into(),
2238 kind: ExportType::Stream,
2239 share: false,
2240 allow_trace: true,
2241 }],
2242 exports: vec![AccountExport {
2243 name: "lookup".into(),
2244 subject: "svc.lookup".into(),
2245 kind: ExportType::Service,
2246 token_req: true,
2247 revocations: export_revocations,
2248 response_type: Some(ResponseType::Stream),
2249 response_threshold: 1_000_000,
2250 service_latency: Some(ServiceLatency {
2251 sampling: SamplingRate::Headers,
2252 results: "latency.results".into(),
2253 }),
2254 account_token_position: 0,
2255 advertise: true,
2256 allow_trace: true,
2257 description: "lookup service".into(),
2258 info_url: "https://example.test/lookup".into(),
2259 }],
2260 limits: OperatorLimits {
2261 nats: NatsLimits {
2262 subs: 100,
2263 data: 1_024,
2264 payload: 256,
2265 },
2266 account: AccountLimits {
2267 imports: 10,
2268 exports: 11,
2269 wildcards: true,
2270 disallow_bearer: true,
2271 conn: 12,
2272 leaf: 13,
2273 },
2274 jetstream: JetStreamLimits::default(),
2275 tiered_limits,
2276 },
2277 signing_keys: Vec::new(),
2278 revocations,
2279 default_permissions: Some(Permissions {
2280 publish: Permission {
2281 allow: vec!["pub.>".into()],
2282 deny: vec!["pub.secret".into()],
2283 },
2284 sub: Permission {
2285 allow: vec!["sub.>".into()],
2286 deny: Vec::new(),
2287 },
2288 resp: Some(ResponsePermission {
2289 max: 1,
2290 ttl: 2_000_000_000,
2291 }),
2292 }),
2293 mappings: BTreeMap::from([(
2294 "legacy.>".into(),
2295 vec![WeightedMapping {
2296 subject: "modern.>".into(),
2297 weight: 50,
2298 cluster: "edge".into(),
2299 }],
2300 )]),
2301 authorization: ExternalAuthorization {
2302 auth_users: vec![user.public_key()],
2303 allowed_accounts: vec![account.public_key()],
2304 xkey: encode_public(NkeyType::Curve, &[9; 32]).unwrap(),
2305 },
2306 trace: Some(MsgTrace {
2307 dest: "trace.out".into(),
2308 sampling: 25,
2309 }),
2310 cluster_traffic: Some(ClusterTraffic::System),
2311 },
2312 };
2313
2314 let signing_input = jwt.signing_input().expect("signing input");
2315 let parts: Vec<&str> = signing_input.split('.').collect();
2316 let claims: serde_json::Value =
2317 serde_json::from_slice(&URL_SAFE_NO_PAD.decode(parts[1]).unwrap()).unwrap();
2318 let nats = &claims["nats"];
2319
2320 assert_eq!(nats["type"], "account");
2321 assert_eq!(nats["imports"][0]["type"], "stream");
2322 assert_eq!(nats["imports"][0]["local_subject"], "tenant.$1");
2323 assert_eq!(nats["exports"][0]["type"], "service");
2324 assert_eq!(nats["exports"][0]["response_type"], "Stream");
2325 assert_eq!(nats["exports"][0]["service_latency"]["sampling"], "headers");
2326 assert_eq!(nats["limits"]["imports"], 10);
2327 assert_eq!(nats["limits"]["wildcards"], true);
2328 assert_eq!(nats["limits"]["tiered_limits"]["gold"]["mem_storage"], 64);
2329 assert_eq!(nats["revocations"][user.public_key()], 1_782_000_001);
2330 assert_eq!(nats["default_permissions"]["pub"]["allow"][0], "pub.>");
2331 assert_eq!(nats["default_permissions"]["resp"]["ttl"], 2_000_000_000i64);
2332 assert_eq!(nats["mappings"]["legacy.>"][0]["subject"], "modern.>");
2333 assert_eq!(nats["authorization"]["auth_users"][0], user.public_key());
2334 assert_eq!(nats["trace"]["dest"], "trace.out");
2335 assert_eq!(nats["cluster_traffic"], "system");
2336 assert_eq!(nats["signing_keys"][0], signing.public_key());
2337 }
2338
2339 #[test]
2340 fn unlimited_operator_limits_serialize_as_a_real_not_deny_all_limits_block() {
2341 let operator = KeyPair::new_operator();
2347 let account = KeyPair::new_account();
2348 let jwt = AccountJwt {
2349 issuer: operator.public_key(),
2350 subject_account: account.public_key(),
2351 name: "tenant".into(),
2352 issued_at: 1_782_000_000,
2353 expires: None,
2354 signing_keys: Vec::new(),
2355 claims: AccountClaims {
2356 limits: OperatorLimits::unlimited(),
2357 ..AccountClaims::default()
2358 },
2359 };
2360
2361 let signing_input = jwt.signing_input().expect("signing input");
2362 let parts: Vec<&str> = signing_input.split('.').collect();
2363 let claims: serde_json::Value =
2364 serde_json::from_slice(&URL_SAFE_NO_PAD.decode(parts[1]).unwrap()).unwrap();
2365 let limits = &claims["nats"]["limits"];
2366
2367 assert!(limits.is_object());
2369 assert_eq!(limits["conn"], -1);
2370 assert_eq!(limits["subs"], -1);
2371 assert_eq!(limits["leaf"], -1);
2372 assert_eq!(limits["data"], -1);
2373 assert_eq!(limits["payload"], -1);
2374 assert_eq!(limits["imports"], -1);
2375 assert_eq!(limits["exports"], -1);
2376 assert_eq!(limits["wildcards"], true);
2377 assert!(limits["mem_storage"].is_null());
2379 }
2380
2381 #[test]
2382 fn operator_jwt_serializes_service_urls_and_strict_signing_keys() {
2383 let operator = KeyPair::new_operator();
2384 let signing = KeyPair::new_operator();
2385
2386 let jwt = OperatorJwt {
2387 issuer: operator.public_key(),
2388 subject_operator: operator.public_key(),
2389 name: "root".into(),
2390 issued_at: 1_782_000_000,
2391 expires: None,
2392 signing_keys: vec![signing.public_key()],
2393 account_server_url: "https://accounts.example.test/jwt/v1".into(),
2394 system_account: String::new(),
2395 claims: OperatorClaims {
2396 operator_service_urls: vec![
2397 "nats://nats.example.test:4222".into(),
2398 "tls://nats.example.test:4443".into(),
2399 ],
2400 assert_server_version: "2.11.0".into(),
2401 strict_signing_key_usage: true,
2402 ..OperatorClaims::default()
2403 },
2404 };
2405
2406 let signing_input = jwt.signing_input().expect("signing input");
2407 let parts: Vec<&str> = signing_input.split('.').collect();
2408 let claims: serde_json::Value =
2409 serde_json::from_slice(&URL_SAFE_NO_PAD.decode(parts[1]).unwrap()).unwrap();
2410 let nats = &claims["nats"];
2411
2412 assert_eq!(nats["type"], "operator");
2413 assert_eq!(nats["signing_keys"][0], signing.public_key());
2414 assert_eq!(
2415 nats["account_server_url"],
2416 "https://accounts.example.test/jwt/v1"
2417 );
2418 assert_eq!(
2419 nats["operator_service_urls"][0],
2420 "nats://nats.example.test:4222"
2421 );
2422 assert_eq!(nats["assert_server_version"], "2.11.0");
2423 assert_eq!(nats["strict_signing_key_usage"], true);
2424 }
2425
2426 #[test]
2427 fn role_jwt_verifies_and_carries_kind() {
2428 let operator = KeyPair::new_operator();
2429 let server = KeyPair::new_server();
2430 let jwt = RoleJwt {
2431 issuer: operator.public_key(),
2432 subject: server.public_key(),
2433 name: "nats-server".into(),
2434 issued_at: 1_782_000_000,
2435 expires: Some(1_782_003_600),
2436 kind: RoleKind::Server,
2437 };
2438
2439 let signing_input = jwt.signing_input().expect("signing input");
2440 let signature = operator.sign(signing_input.as_bytes()).expect("sign");
2441 let token = assemble(&signing_input, &signature);
2442
2443 let parts: Vec<&str> = token.split('.').collect();
2444 let claims: serde_json::Value =
2445 serde_json::from_slice(&URL_SAFE_NO_PAD.decode(parts[1]).unwrap()).unwrap();
2446 assert_eq!(claims["iss"], operator.public_key());
2447 assert_eq!(claims["sub"], server.public_key());
2448 assert_eq!(claims["nats"]["type"], "server");
2449 assert_eq!(claims["nats"]["version"], 2);
2450
2451 let issuer = KeyPair::from_public_key(claims["iss"].as_str().unwrap()).unwrap();
2452 issuer
2453 .verify(signing_input.as_bytes(), &signature)
2454 .expect("must verify under iss operator key");
2455 }
2456
2457 #[test]
2458 fn decode_and_validate_known_good_user_account_operator_tokens() {
2459 let account = KeyPair::new_account();
2460 let user = KeyPair::new_user();
2461 let user_jwt = UserJwt {
2462 issuer: account.public_key(),
2463 issuer_account: None,
2464 subject_user: user.public_key(),
2465 name: "bob".into(),
2466 issued_at: 1_782_000_000,
2467 expires: Some(1_782_003_600),
2468 permissions: UserPermissions::default(),
2469 };
2470 let user_input = user_jwt.signing_input().expect("user input");
2471 let user_token = signed_token(&user_input, &account);
2472 assert_valid_token(&user_token, &account, "user");
2473
2474 let operator = KeyPair::new_operator();
2475 let tenant = KeyPair::new_account();
2476 let account_jwt = AccountJwt {
2477 issuer: operator.public_key(),
2478 subject_account: tenant.public_key(),
2479 name: "tenant".into(),
2480 issued_at: 1_782_000_000,
2481 expires: Some(1_782_003_600),
2482 signing_keys: Vec::new(),
2483 claims: AccountClaims::default(),
2484 };
2485 let account_input = account_jwt.signing_input().expect("account input");
2486 let account_token = signed_token(&account_input, &operator);
2487 assert_valid_token(&account_token, &operator, "account");
2488
2489 let operator_jwt = OperatorJwt {
2490 issuer: operator.public_key(),
2491 subject_operator: operator.public_key(),
2492 name: "root".into(),
2493 issued_at: 1_782_000_000,
2494 expires: Some(1_782_003_600),
2495 signing_keys: Vec::new(),
2496 account_server_url: String::new(),
2497 system_account: String::new(),
2498 claims: OperatorClaims::default(),
2499 };
2500 let operator_input = operator_jwt.signing_input().expect("operator input");
2501 let operator_token = signed_token(&operator_input, &operator);
2502 assert_valid_token(&operator_token, &operator, "operator");
2503 }
2504
2505 #[test]
2506 fn validation_rejects_expired_and_not_yet_valid_tokens() {
2507 let account = KeyPair::new_account();
2508 let user = KeyPair::new_user();
2509 let expired = UserJwt {
2510 issuer: account.public_key(),
2511 issuer_account: None,
2512 subject_user: user.public_key(),
2513 name: "old".into(),
2514 issued_at: 100,
2515 expires: Some(200),
2516 permissions: UserPermissions::default(),
2517 };
2518 let expired_input = expired.signing_input().expect("expired input");
2519 let expired_token = signed_token(&expired_input, &account);
2520 let decoded_expired = decode_nats_jwt(&expired_token).expect("decode expired");
2521 let account_public = account.public_key();
2522 let expired_validation = decoded_expired
2523 .verify_with_candidates([CandidateSigner::Nkey(&account_public)], 200)
2524 .expect("validate expired");
2525 assert_eq!(expired_validation.reason, NatsJwtValidationReason::Expired);
2526 assert!(!expired_validation.is_valid());
2527 assert!(expired_validation.matched_signer.is_none());
2528
2529 let claims = json!({
2530 "jti": "manual",
2531 "iat": 100_u64,
2532 "iss": account.public_key(),
2533 "name": "future",
2534 "nbf": 300_u64,
2535 "nats": {"type": "user", "version": 2},
2536 "sub": user.public_key(),
2537 });
2538 let future_input = signing_input_from_claims(&claims).expect("future input");
2539 let future_token = signed_token(&future_input, &account);
2540 let decoded_future = decode_nats_jwt(&future_token).expect("decode future");
2541 let future_validation = decoded_future
2542 .verify_with_candidates([CandidateSigner::Nkey(&account_public)], 299)
2543 .expect("validate future");
2544 assert_eq!(
2545 future_validation.reason,
2546 NatsJwtValidationReason::NotYetValid
2547 );
2548 assert!(!future_validation.is_valid());
2549 assert!(future_validation.matched_signer.is_none());
2550 }
2551
2552 #[test]
2553 fn validation_rejects_tampered_signature_and_unknown_signer() {
2554 let account = KeyPair::new_account();
2555 let other = KeyPair::new_account();
2556 let user = KeyPair::new_user();
2557 let jwt = UserJwt {
2558 issuer: account.public_key(),
2559 issuer_account: None,
2560 subject_user: user.public_key(),
2561 name: "bob".into(),
2562 issued_at: 1_782_000_000,
2563 expires: None,
2564 permissions: UserPermissions::default(),
2565 };
2566 let input = jwt.signing_input().expect("input");
2567 let mut signature = account.sign(input.as_bytes()).expect("sign");
2568 signature[0] ^= 1;
2569 let tampered = assemble(&input, &signature);
2570
2571 let account_public = account.public_key();
2572 let decoded_tampered = decode_nats_jwt(&tampered).expect("decode tampered");
2573 let bad_signature = decoded_tampered
2574 .verify_with_candidates([CandidateSigner::Nkey(&account_public)], 1_782_000_000)
2575 .expect("validate tampered");
2576 assert_eq!(bad_signature.reason, NatsJwtValidationReason::BadSignature);
2577 assert!(!bad_signature.is_valid());
2578 assert!(bad_signature.matched_signer.is_none());
2579
2580 let valid = signed_token(&input, &account);
2581 let decoded_valid = decode_nats_jwt(&valid).expect("decode valid");
2582 let other_public = other.public_key();
2583 let unknown = decoded_valid
2584 .verify_with_candidates([CandidateSigner::Nkey(&other_public)], 1_782_000_000)
2585 .expect("validate unknown");
2586 assert_eq!(unknown.reason, NatsJwtValidationReason::UnknownSigner);
2587 assert!(!unknown.is_valid());
2588 assert!(unknown.matched_signer.is_none());
2589 }
2590
2591 #[test]
2592 fn public_nkey_verifies_raw_signature() {
2593 let user = KeyPair::new_user();
2594 let message = b"basil sealed invocation digest";
2595 let signature = user.sign(message).expect("sign");
2596 assert!(
2597 verify_public_signature(&user.public_key(), message, &signature)
2598 .expect("valid public nkey")
2599 );
2600
2601 let other = KeyPair::new_user();
2602 assert!(
2603 !verify_public_signature(&other.public_key(), message, &signature)
2604 .expect("valid other public nkey")
2605 );
2606 assert!(matches!(
2607 verify_public_signature(&user.public_key(), message, &[0_u8; 63]),
2608 Err(Error::BadSignatureLen(63))
2609 ));
2610 assert!(matches!(
2611 verify_public_signature("not-an-nkey", message, &signature),
2612 Err(Error::BadEncoding | Error::BadPublicKeyLen(_) | Error::UnsupportedPrefix(_))
2613 ));
2614 }
2615
2616 #[test]
2617 fn decode_rejects_malformed_tokens() {
2618 assert!(matches!(
2619 decode_nats_jwt("not-a-jwt"),
2620 Err(Error::MalformedJwt(_))
2621 ));
2622
2623 let claims = URL_SAFE_NO_PAD.encode(br#"{"iss":"O","sub":"U"}"#);
2624 let bad_header = format!(
2625 "{}.{}.{}",
2626 URL_SAFE_NO_PAD.encode(br#"{"alg":"none"}"#),
2627 claims,
2628 ""
2629 );
2630 assert!(matches!(
2631 decode_nats_jwt(&bad_header),
2632 Err(Error::MalformedJwt(_))
2633 ));
2634
2635 let header = URL_SAFE_NO_PAD.encode(HEADER_JSON.as_bytes());
2636 let bad_signature = format!("{header}.{claims}.{}", URL_SAFE_NO_PAD.encode([1_u8, 2]));
2637 assert!(matches!(
2638 decode_nats_jwt(&bad_signature),
2639 Err(Error::MalformedJwt(_) | Error::BadSignatureLen(_))
2640 ));
2641 }
2642}