1use std::time::{SystemTime, UNIX_EPOCH};
13
14use base64::Engine;
15use base64::engine::general_purpose::URL_SAFE_NO_PAD;
16use p256::elliptic_curve::sec1::ToEncodedPoint as _;
17use rsa::pkcs1::DecodeRsaPublicKey;
18use rsa::pkcs8::DecodePublicKey;
19use rsa::traits::PublicKeyParts;
20use serde_json::{Value, json};
21use sha2::{Digest, Sha256};
22
23use basil_nats::{NkeyType, RoleKind};
24
25use crate::backend::{Backend, BackendError, SignOptions};
26
27const RESERVED_GENERIC_CLAIMS: &[&str] = &["iss", "iat", "exp", "jti", "sub", "nbf"];
30
31const RESERVED_SVID_CLAIMS: &[&str] = &["iss", "iat", "exp", "jti", "sub", "nbf", "aud"];
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum NatsJwtKind {
40 User,
42 Account,
44 Operator,
46 Signer,
48 Server,
50 Curve,
52}
53
54impl NatsJwtKind {
55 #[must_use]
57 pub fn parse(value: &str) -> Option<Self> {
58 match value {
59 "user" => Some(Self::User),
60 "account" => Some(Self::Account),
61 "operator" => Some(Self::Operator),
62 "signer" => Some(Self::Signer),
63 "server" => Some(Self::Server),
64 "curve" => Some(Self::Curve),
65 _ => None,
66 }
67 }
68
69 #[must_use]
71 pub const fn as_str(self) -> &'static str {
72 match self {
73 Self::User => "user",
74 Self::Account => "account",
75 Self::Operator => "operator",
76 Self::Signer => "signer",
77 Self::Server => "server",
78 Self::Curve => "curve",
79 }
80 }
81}
82
83impl std::fmt::Display for NatsJwtKind {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 f.write_str(self.as_str())
86 }
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub enum NatsJtiMode {
92 RequireValid,
94 Rewrite,
96}
97
98pub struct SignNatsJwtSpec<'a> {
100 pub signing_key_id: &'a str,
102 pub issuer_role: NkeyType,
104 pub claims: &'a Value,
106 pub expected_kind: Option<NatsJwtKind>,
108 pub issued_at: Option<u64>,
110 pub expires_at: Option<u64>,
112 pub jti_mode: NatsJtiMode,
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub enum SvidAlg {
124 EdDsa,
126 Rs256,
128 Es256,
130 Es384,
132}
133
134impl SvidAlg {
135 const fn header_alg(self) -> &'static str {
137 match self {
138 Self::EdDsa => "EdDSA",
139 Self::Rs256 => "RS256",
140 Self::Es256 => "ES256",
141 Self::Es384 => "ES384",
142 }
143 }
144
145 const fn sign_options(self) -> SignOptions {
147 match self {
148 Self::EdDsa => SignOptions::Default,
149 Self::Rs256 => SignOptions::Rs256Pkcs1v15Sha256,
150 Self::Es256 => SignOptions::Es256,
151 Self::Es384 => SignOptions::Es384,
152 }
153 }
154}
155
156pub async fn jwt_svid_jwks(
163 backend: &dyn Backend,
164 signing_key_id: &str,
165 alg: SvidAlg,
166) -> Result<Vec<u8>, BackendError> {
167 let public_key = backend.public_key(signing_key_id).await?;
168 jwt_svid_jwks_from_public_key(&public_key, alg)
169}
170
171pub async fn jwt_svid_jwks_grace(
184 backend: &dyn Backend,
185 signing_key_id: &str,
186 alg: SvidAlg,
187 grace_floor: impl Fn(u32) -> u32,
188) -> Result<Vec<u8>, BackendError> {
189 let versions = backend.public_keys(signing_key_id).await?;
190 let latest = versions.keys().copied().max().unwrap_or(1);
191 jwt_svid_jwks_grace_window(&versions, latest, grace_floor(latest), alg)
192}
193
194pub fn jwt_svid_jwks_from_public_key(
199 public_key: &[u8],
200 alg: SvidAlg,
201) -> Result<Vec<u8>, BackendError> {
202 let key = jwk_for_public_key(public_key, alg)?;
203 serde_json::to_vec(&json!({ "keys": [key] }))
204 .map_err(|e| BackendError::Protocol(format!("serializing jwks: {e}")))
205}
206
207pub fn jwt_svid_jwks_grace_window(
227 versions: &std::collections::BTreeMap<u32, Vec<u8>>,
228 latest: u32,
229 grace_floor: u32,
230 alg: SvidAlg,
231) -> Result<Vec<u8>, BackendError> {
232 let mut keys: Vec<Value> = Vec::new();
233 let mut seen_kids: Vec<String> = Vec::new();
234 for (&version, public_key) in versions {
235 if version < grace_floor || version > latest {
238 continue;
239 }
240 let kid = jwt_svid_jwk_kid(public_key, alg);
241 if seen_kids.iter().any(|k| k == &kid) {
242 continue;
243 }
244 seen_kids.push(kid);
245 keys.push(jwk_for_public_key(public_key, alg)?);
246 }
247 serde_json::to_vec(&json!({ "keys": keys }))
248 .map_err(|e| BackendError::Protocol(format!("serializing jwks: {e}")))
249}
250
251fn jwk_for_public_key(public_key: &[u8], alg: SvidAlg) -> Result<Value, BackendError> {
255 let kid = jwt_svid_jwk_kid(public_key, alg);
256 match alg {
257 SvidAlg::EdDsa => {
258 if public_key.len() != 32 {
259 return Err(BackendError::Protocol(format!(
260 "Ed25519 JWT issuer public key must be 32 bytes, got {}",
261 public_key.len()
262 )));
263 }
264 Ok(json!({
265 "kty": "OKP",
266 "crv": "Ed25519",
267 "x": URL_SAFE_NO_PAD.encode(public_key),
268 "use": "sig",
269 "alg": alg.header_alg(),
270 "kid": kid,
271 }))
272 }
273 SvidAlg::Rs256 => {
274 let key = decode_rsa_public_key(public_key)?;
275 Ok(json!({
276 "kty": "RSA",
277 "n": URL_SAFE_NO_PAD.encode(key.n().to_bytes_be()),
278 "e": URL_SAFE_NO_PAD.encode(key.e().to_bytes_be()),
279 "use": "sig",
280 "alg": alg.header_alg(),
281 "kid": kid,
282 }))
283 }
284 SvidAlg::Es256 => {
285 let key = decode_p256_public_key(public_key)?;
286 let encoded = key.to_encoded_point(false);
287 let x = encoded.x().ok_or_else(|| {
288 BackendError::Protocol("P-256 public key has no x coordinate".into())
289 })?;
290 let y = encoded.y().ok_or_else(|| {
291 BackendError::Protocol("P-256 public key has no y coordinate".into())
292 })?;
293 Ok(json!({
294 "kty": "EC",
295 "crv": "P-256",
296 "x": URL_SAFE_NO_PAD.encode(x),
297 "y": URL_SAFE_NO_PAD.encode(y),
298 "use": "sig",
299 "alg": alg.header_alg(),
300 "kid": kid,
301 }))
302 }
303 SvidAlg::Es384 => {
304 let key = decode_p384_public_key(public_key)?;
305 let encoded = key.to_encoded_point(false);
306 let x = encoded.x().ok_or_else(|| {
307 BackendError::Protocol("P-384 public key has no x coordinate".into())
308 })?;
309 let y = encoded.y().ok_or_else(|| {
310 BackendError::Protocol("P-384 public key has no y coordinate".into())
311 })?;
312 Ok(json!({
313 "kty": "EC",
314 "crv": "P-384",
315 "x": URL_SAFE_NO_PAD.encode(x),
316 "y": URL_SAFE_NO_PAD.encode(y),
317 "use": "sig",
318 "alg": alg.header_alg(),
319 "kid": kid,
320 }))
321 }
322 }
323}
324
325#[must_use]
327pub fn jwt_svid_jwk_kid(public_key: &[u8], alg: SvidAlg) -> String {
328 let mut hasher = Sha256::new();
329 hasher.update(alg.header_alg().as_bytes());
330 hasher.update([0]);
331 hasher.update(public_key);
332 URL_SAFE_NO_PAD.encode(hasher.finalize())
333}
334
335fn decode_rsa_public_key(public_key: &[u8]) -> Result<rsa::RsaPublicKey, BackendError> {
336 if let Ok(pem) = std::str::from_utf8(public_key)
337 && pem.trim_start().starts_with("-----BEGIN ")
338 {
339 return rsa::RsaPublicKey::from_public_key_pem(pem)
340 .or_else(|_| rsa::RsaPublicKey::from_pkcs1_pem(pem))
341 .map_err(|e| BackendError::Protocol(format!("RSA public key PEM is malformed: {e}")));
342 }
343
344 rsa::RsaPublicKey::from_public_key_der(public_key)
345 .or_else(|_| rsa::RsaPublicKey::from_pkcs1_der(public_key))
346 .map_err(|e| BackendError::Protocol(format!("RSA public key DER is malformed: {e}")))
347}
348
349fn decode_p256_public_key(public_key: &[u8]) -> Result<p256::PublicKey, BackendError> {
350 if let Ok(pem) = std::str::from_utf8(public_key)
351 && pem.trim_start().starts_with("-----BEGIN ")
352 {
353 return p256::PublicKey::from_public_key_pem(pem).map_err(|e| {
354 BackendError::Protocol(format!("P-256 public key PEM is malformed: {e}"))
355 });
356 }
357
358 p256::PublicKey::from_public_key_der(public_key)
359 .map_err(|e| BackendError::Protocol(format!("P-256 public key DER is malformed: {e}")))
360}
361
362fn decode_p384_public_key(public_key: &[u8]) -> Result<p384::PublicKey, BackendError> {
363 if let Ok(pem) = std::str::from_utf8(public_key)
364 && pem.trim_start().starts_with("-----BEGIN ")
365 {
366 return p384::PublicKey::from_public_key_pem(pem).map_err(|e| {
367 BackendError::Protocol(format!("P-384 public key PEM is malformed: {e}"))
368 });
369 }
370
371 p384::PublicKey::from_public_key_der(public_key)
372 .map_err(|e| BackendError::Protocol(format!("P-384 public key DER is malformed: {e}")))
373}
374
375#[derive(Debug)]
378pub struct ReservedClaim(pub String);
379
380impl std::fmt::Display for ReservedClaim {
381 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
382 write!(f, "claims may not override the reserved claim `{}`", self.0)
383 }
384}
385impl std::error::Error for ReservedClaim {}
386
387fn unix_now() -> Result<u64, BackendError> {
389 Ok(SystemTime::now()
390 .duration_since(UNIX_EPOCH)
391 .map_err(|e| BackendError::Backend(e.to_string()))?
392 .as_secs())
393}
394
395#[allow(clippy::too_many_arguments)] pub async fn mint_nats_user(
411 backend: &dyn Backend,
412 signing_key_id: &str,
413 issuer_role: NkeyType,
414 subject_user_nkey: &str,
415 issuer_account: Option<&str>,
416 name: &str,
417 expires_in_secs: Option<u64>,
418 permissions: basil_nats::UserPermissions,
419) -> Result<String, BackendError> {
420 ensure_issuer_role(issuer_role, &[NkeyType::Account], "user")?;
421 basil_nats::require_public_prefix(subject_user_nkey, NkeyType::User)
423 .map_err(|e| BackendError::Protocol(format!("invalid subject user nkey: {e}")))?;
424 let issuer_account = match issuer_account {
426 Some(account) => {
427 basil_nats::require_public_prefix(account, NkeyType::Account)
428 .map_err(|e| BackendError::Protocol(format!("invalid issuer account nkey: {e}")))?;
429 Some(account.to_string())
430 }
431 None => None,
432 };
433
434 let public = backend.public_key(signing_key_id).await?;
436 let issuer = basil_nats::encode_public(issuer_role, &public)
437 .map_err(|e| BackendError::Protocol(format!("deriving issuer nkey: {e}")))?;
438
439 let now = unix_now()?;
440
441 let jwt = basil_nats::UserJwt {
442 issuer,
443 issuer_account,
444 subject_user: subject_user_nkey.to_string(),
445 name: name.to_string(),
446 issued_at: now,
447 expires: expires_in_secs.map(|s| now.saturating_add(s)),
448 permissions,
449 };
450 let signing_input = jwt
451 .signing_input()
452 .map_err(|e| BackendError::Protocol(format!("building nats jwt: {e}")))?;
453
454 let signature = backend
456 .sign(signing_key_id, signing_input.as_bytes())
457 .await?;
458
459 Ok(basil_nats::assemble(&signing_input, &signature))
460}
461
462pub async fn mint_nats_account(
469 backend: &dyn Backend,
470 signing_key_id: &str,
471 issuer_role: NkeyType,
472 subject_account_nkey: &str,
473 name: &str,
474 expires_in_secs: Option<u64>,
475 signing_keys: Vec<String>,
476) -> Result<String, BackendError> {
477 ensure_issuer_role(
478 issuer_role,
479 &[NkeyType::Account, NkeyType::Operator],
480 "account",
481 )?;
482 basil_nats::require_public_prefix(subject_account_nkey, NkeyType::Account)
483 .map_err(|e| BackendError::Protocol(format!("invalid subject account nkey: {e}")))?;
484 for key in &signing_keys {
485 basil_nats::require_public_prefix(key, NkeyType::Account)
486 .map_err(|e| BackendError::Protocol(format!("invalid account signing key: {e}")))?;
487 }
488
489 let public = backend.public_key(signing_key_id).await?;
490 let issuer = basil_nats::encode_public(issuer_role, &public)
491 .map_err(|e| BackendError::Protocol(format!("deriving issuer nkey: {e}")))?;
492
493 let now = unix_now()?;
494 let jwt = basil_nats::AccountJwt {
495 issuer,
496 subject_account: subject_account_nkey.to_string(),
497 name: name.to_string(),
498 issued_at: now,
499 expires: expires_in_secs.map(|s| now.saturating_add(s)),
500 signing_keys,
501 claims: basil_nats::AccountClaims {
506 limits: basil_nats::OperatorLimits::unlimited(),
507 ..basil_nats::AccountClaims::default()
508 },
509 };
510 let signing_input = jwt
511 .signing_input()
512 .map_err(|e| BackendError::Protocol(format!("building nats account jwt: {e}")))?;
513 let signature = backend
514 .sign(signing_key_id, signing_input.as_bytes())
515 .await?;
516 Ok(basil_nats::assemble(&signing_input, &signature))
517}
518
519#[allow(clippy::too_many_arguments)] pub async fn mint_nats_operator(
527 backend: &dyn Backend,
528 signing_key_id: &str,
529 issuer_role: NkeyType,
530 subject_operator_nkey: Option<&str>,
531 name: &str,
532 expires_in_secs: Option<u64>,
533 signing_keys: Vec<String>,
534 account_server_url: String,
535 system_account: String,
536) -> Result<String, BackendError> {
537 ensure_issuer_role(issuer_role, &[NkeyType::Operator], "operator")?;
538 let public = backend.public_key(signing_key_id).await?;
539 let issuer = basil_nats::encode_public(issuer_role, &public)
540 .map_err(|e| BackendError::Protocol(format!("deriving issuer nkey: {e}")))?;
541
542 let subject_operator = match subject_operator_nkey {
545 Some(s) => {
546 basil_nats::require_public_prefix(s, NkeyType::Operator).map_err(|e| {
547 BackendError::Protocol(format!("invalid subject operator nkey: {e}"))
548 })?;
549 s.to_string()
550 }
551 None => issuer.clone(),
552 };
553 for key in &signing_keys {
554 basil_nats::require_public_prefix(key, NkeyType::Operator)
555 .map_err(|e| BackendError::Protocol(format!("invalid operator signing key: {e}")))?;
556 }
557 if !system_account.is_empty() {
558 basil_nats::require_public_prefix(&system_account, NkeyType::Account)
559 .map_err(|e| BackendError::Protocol(format!("invalid system account nkey: {e}")))?;
560 }
561
562 let now = unix_now()?;
563 let jwt = basil_nats::OperatorJwt {
564 issuer,
565 subject_operator,
566 name: name.to_string(),
567 issued_at: now,
568 expires: expires_in_secs.map(|s| now.saturating_add(s)),
569 signing_keys,
570 account_server_url,
571 system_account,
572 claims: basil_nats::OperatorClaims::default(),
573 };
574 let signing_input = jwt
575 .signing_input()
576 .map_err(|e| BackendError::Protocol(format!("building nats operator jwt: {e}")))?;
577 let signature = backend
578 .sign(signing_key_id, signing_input.as_bytes())
579 .await?;
580 Ok(basil_nats::assemble(&signing_input, &signature))
581}
582
583fn ensure_issuer_role(
584 issuer: NkeyType,
585 allowed: &[NkeyType],
586 claim_kind: &'static str,
587) -> Result<(), BackendError> {
588 if allowed.contains(&issuer) {
589 return Ok(());
590 }
591 let expected = allowed.iter().map(|role| role.letter()).collect::<String>();
592 Err(BackendError::Protocol(format!(
593 "unsupported issuer role {issuer} for nats {claim_kind}; expected one of {expected}"
594 )))
595}
596
597async fn mint_nats_role(
598 backend: &dyn Backend,
599 spec: RoleMintSpec<'_>,
600) -> Result<String, BackendError> {
601 let RoleMintSpec {
602 signing_key_id,
603 issuer_role,
604 subject_nkey,
605 subject_role,
606 name,
607 expires_in_secs,
608 kind,
609 } = spec;
610 ensure_issuer_role(issuer_role, &[subject_role], kind.as_str())?;
611 basil_nats::require_public_prefix(subject_nkey, subject_role).map_err(|e| {
612 BackendError::Protocol(format!("invalid subject {} nkey: {e}", kind.as_str()))
613 })?;
614
615 let public = backend.public_key(signing_key_id).await?;
616 let issuer = basil_nats::encode_public(issuer_role, &public)
617 .map_err(|e| BackendError::Protocol(format!("deriving issuer nkey: {e}")))?;
618 let now = unix_now()?;
619 let jwt = basil_nats::RoleJwt {
620 issuer,
621 subject: subject_nkey.to_string(),
622 name: name.to_string(),
623 issued_at: now,
624 expires: expires_in_secs.map(|s| now.saturating_add(s)),
625 kind,
626 };
627 let signing_input = jwt
628 .signing_input()
629 .map_err(|e| BackendError::Protocol(format!("building nats {} jwt: {e}", kind.as_str())))?;
630 let signature = backend
631 .sign(signing_key_id, signing_input.as_bytes())
632 .await?;
633 Ok(basil_nats::assemble(&signing_input, &signature))
634}
635
636struct RoleMintSpec<'a> {
637 signing_key_id: &'a str,
638 issuer_role: NkeyType,
639 subject_nkey: &'a str,
640 subject_role: NkeyType,
641 name: &'a str,
642 expires_in_secs: Option<u64>,
643 kind: RoleKind,
644}
645
646pub async fn mint_nats_signer(
647 backend: &dyn Backend,
648 signing_key_id: &str,
649 issuer_role: NkeyType,
650 subject_nkey: &str,
651 name: &str,
652 expires_in_secs: Option<u64>,
653) -> Result<String, BackendError> {
654 ensure_issuer_role(
655 issuer_role,
656 &[NkeyType::Account, NkeyType::Operator],
657 "signer",
658 )?;
659 basil_nats::require_public_prefix(subject_nkey, issuer_role)
662 .map_err(|e| BackendError::Protocol(format!("invalid subject signer nkey: {e}")))?;
663
664 let public = backend.public_key(signing_key_id).await?;
665 let issuer = basil_nats::encode_public(issuer_role, &public)
666 .map_err(|e| BackendError::Protocol(format!("deriving issuer nkey: {e}")))?;
667 let now = unix_now()?;
668 let jwt = basil_nats::RoleJwt {
669 issuer,
670 subject: subject_nkey.to_string(),
671 name: name.to_string(),
672 issued_at: now,
673 expires: expires_in_secs.map(|s| now.saturating_add(s)),
674 kind: RoleKind::Signer,
675 };
676 let signing_input = jwt
677 .signing_input()
678 .map_err(|e| BackendError::Protocol(format!("building nats signer jwt: {e}")))?;
679 let signature = backend
680 .sign(signing_key_id, signing_input.as_bytes())
681 .await?;
682 Ok(basil_nats::assemble(&signing_input, &signature))
683}
684
685pub async fn mint_nats_server(
686 backend: &dyn Backend,
687 signing_key_id: &str,
688 issuer_role: NkeyType,
689 subject_server_nkey: &str,
690 name: &str,
691 expires_in_secs: Option<u64>,
692) -> Result<String, BackendError> {
693 mint_nats_role(
694 backend,
695 RoleMintSpec {
696 signing_key_id,
697 issuer_role,
698 subject_nkey: subject_server_nkey,
699 subject_role: NkeyType::Server,
700 name,
701 expires_in_secs,
702 kind: RoleKind::Server,
703 },
704 )
705 .await
706}
707
708pub async fn mint_nats_curve(
709 backend: &dyn Backend,
710 signing_key_id: &str,
711 issuer_role: NkeyType,
712 subject_curve_nkey: &str,
713 name: &str,
714 expires_in_secs: Option<u64>,
715) -> Result<String, BackendError> {
716 mint_nats_role(
717 backend,
718 RoleMintSpec {
719 signing_key_id,
720 issuer_role,
721 subject_nkey: subject_curve_nkey,
722 subject_role: NkeyType::Curve,
723 name,
724 expires_in_secs,
725 kind: RoleKind::Curve,
726 },
727 )
728 .await
729}
730
731pub async fn sign_nats_jwt(
739 backend: &dyn Backend,
740 spec: SignNatsJwtSpec<'_>,
741) -> Result<String, BackendError> {
742 let SignNatsJwtSpec {
743 signing_key_id,
744 issuer_role,
745 claims,
746 expected_kind,
747 issued_at,
748 expires_at,
749 jti_mode,
750 } = spec;
751 let mut claims = claims
752 .as_object()
753 .ok_or_else(|| BackendError::Protocol("invalid nats jwt claims: expected object".into()))?
754 .clone();
755
756 let public = backend.public_key(signing_key_id).await?;
757 let issuer = basil_nats::encode_public(issuer_role, &public)
758 .map_err(|e| BackendError::Protocol(format!("deriving issuer nkey: {e}")))?;
759 set_or_validate_string(&mut claims, "iss", &issuer)?;
760
761 let now = unix_now()?;
762 let iat = match issued_at {
763 Some(value) => {
764 claims.insert("iat".into(), Value::Number(value.into()));
765 value
766 }
767 None => {
768 if let Some(value) = claims.get("iat") {
769 claim_u64(value, "iat")?
770 } else {
771 claims.insert("iat".into(), Value::Number(now.into()));
772 now
773 }
774 }
775 };
776 let exp = match expires_at {
777 Some(value) => {
778 claims.insert("exp".into(), Value::Number(value.into()));
779 Some(value)
780 }
781 None => claims
782 .get("exp")
783 .map(|value| claim_u64(value, "exp"))
784 .transpose()?,
785 };
786
787 let sub = required_claim_string(&claims, "sub")?.to_string();
788 let name = optional_claim_string(&claims, "name")?;
789 let aud = optional_claim_string(&claims, "aud")?;
790 let nbf = claims
791 .get("nbf")
792 .map(|value| claim_u64(value, "nbf"))
793 .transpose()?;
794 let kind = validate_nats_claim(&claims, expected_kind)?;
795 validate_nats_roles(issuer_role, kind, &sub)?;
796
797 let computed_jti = basil_nats::jti_for_standard_claims(&issuer, &sub, name, iat, exp, aud, nbf)
798 .map_err(|e| BackendError::Protocol(format!("building nats jwt jti: {e}")))?;
799 match claims.get("jti") {
800 Some(value) if value.as_str() == Some(computed_jti.as_str()) => {}
801 Some(_) if jti_mode == NatsJtiMode::Rewrite => {
802 claims.insert("jti".into(), Value::String(computed_jti));
803 }
804 Some(_) => {
805 return Err(BackendError::Protocol(
806 "invalid nats jwt jti: supplied jti does not match standard claims".into(),
807 ));
808 }
809 None => {
810 claims.insert("jti".into(), Value::String(computed_jti));
811 }
812 }
813
814 let signing_input = basil_nats::signing_input_from_claims(&Value::Object(claims))
815 .map_err(|e| BackendError::Protocol(format!("building nats jwt: {e}")))?;
816 let signature = backend
817 .sign(signing_key_id, signing_input.as_bytes())
818 .await?;
819 Ok(basil_nats::assemble(&signing_input, &signature))
820}
821
822fn set_or_validate_string(
823 claims: &mut serde_json::Map<String, Value>,
824 field: &'static str,
825 expected: &str,
826) -> Result<(), BackendError> {
827 match claims.get(field) {
828 Some(value) if value.as_str() == Some(expected) => Ok(()),
829 Some(_) => Err(BackendError::Protocol(format!(
830 "invalid nats jwt {field}: does not match signing key"
831 ))),
832 None => {
833 claims.insert(field.into(), Value::String(expected.to_string()));
834 Ok(())
835 }
836 }
837}
838
839fn required_claim_string<'a>(
840 claims: &'a serde_json::Map<String, Value>,
841 field: &'static str,
842) -> Result<&'a str, BackendError> {
843 claims
844 .get(field)
845 .and_then(Value::as_str)
846 .filter(|value| !value.is_empty())
847 .ok_or_else(|| BackendError::Protocol(format!("invalid nats jwt {field}: required string")))
848}
849
850fn optional_claim_string<'a>(
851 claims: &'a serde_json::Map<String, Value>,
852 field: &'static str,
853) -> Result<Option<&'a str>, BackendError> {
854 let Some(value) = claims.get(field) else {
855 return Ok(None);
856 };
857 value
858 .as_str()
859 .map(|value| (!value.is_empty()).then_some(value))
860 .ok_or_else(|| BackendError::Protocol(format!("invalid nats jwt {field}: required string")))
861}
862
863fn claim_u64(value: &Value, field: &'static str) -> Result<u64, BackendError> {
864 if let Some(value) = value.as_u64() {
865 return Ok(value);
866 }
867 Err(BackendError::Protocol(format!(
868 "invalid nats jwt {field}: required u64"
869 )))
870}
871
872fn validate_nats_claim(
873 claims: &serde_json::Map<String, Value>,
874 expected_kind: Option<NatsJwtKind>,
875) -> Result<NatsJwtKind, BackendError> {
876 let nats = claims
877 .get("nats")
878 .and_then(Value::as_object)
879 .ok_or_else(|| BackendError::Protocol("invalid nats jwt nats: required object".into()))?;
880 let kind = nats
881 .get("type")
882 .and_then(Value::as_str)
883 .and_then(NatsJwtKind::parse)
884 .ok_or_else(|| {
885 BackendError::Protocol("invalid nats jwt nats.type: unsupported or missing".into())
886 })?;
887 if let Some(expected) = expected_kind
888 && expected != kind
889 {
890 return Err(BackendError::Protocol(format!(
891 "invalid nats jwt nats.type: expected {expected}, got {kind}"
892 )));
893 }
894 let version = nats
895 .get("version")
896 .ok_or_else(|| BackendError::Protocol("invalid nats jwt nats.version: expected 2".into()))
897 .and_then(|value| claim_u64(value, "nats.version"))?;
898 if version != 2 {
899 return Err(BackendError::Protocol(
900 "invalid nats jwt nats.version: expected 2".into(),
901 ));
902 }
903 Ok(kind)
904}
905
906fn validate_nats_roles(
907 issuer_role: NkeyType,
908 kind: NatsJwtKind,
909 subject_nkey: &str,
910) -> Result<(), BackendError> {
911 let allowed_issuers: &[NkeyType] = match kind {
912 NatsJwtKind::User => &[NkeyType::Account],
913 NatsJwtKind::Account | NatsJwtKind::Signer => &[NkeyType::Account, NkeyType::Operator],
914 NatsJwtKind::Operator => &[NkeyType::Operator],
915 NatsJwtKind::Server => &[NkeyType::Server],
916 NatsJwtKind::Curve => &[NkeyType::Curve],
917 };
918 ensure_issuer_role(issuer_role, allowed_issuers, kind.as_str())?;
919
920 let subject_role = match kind {
921 NatsJwtKind::User => NkeyType::User,
922 NatsJwtKind::Account | NatsJwtKind::Signer => NkeyType::Account,
923 NatsJwtKind::Operator => NkeyType::Operator,
924 NatsJwtKind::Server => NkeyType::Server,
925 NatsJwtKind::Curve => NkeyType::Curve,
926 };
927 let subject_role = if kind == NatsJwtKind::Signer {
928 issuer_role
929 } else {
930 subject_role
931 };
932 basil_nats::require_public_prefix(subject_nkey, subject_role)
933 .map_err(|e| BackendError::Protocol(format!("invalid nats jwt sub: {e}")))?;
934 Ok(())
935}
936
937pub async fn mint_generic(
952 backend: &dyn Backend,
953 signing_key_id: &str,
954 issuer_name: &str,
955 subject: &str,
956 expires_in_secs: Option<u64>,
957 extra: &Value,
958) -> Result<String, GenericMintError> {
959 let now = unix_now()?;
961 let exp = expires_in_secs.map(|s| now.saturating_add(s));
962
963 let mut claims = serde_json::Map::new();
964 claims.insert("iss".into(), Value::String(issuer_name.to_string()));
965 claims.insert("sub".into(), Value::String(subject.to_string()));
966 claims.insert("iat".into(), Value::Number(now.into()));
967 if let Some(exp) = exp {
968 claims.insert("exp".into(), Value::Number(exp.into()));
969 }
970
971 claims.insert("jti".into(), Value::String(jti_for(&claims)?));
974
975 let public_key = backend.public_key(signing_key_id).await?;
976 let alg = jws_alg_for_public_key(&public_key)?;
977
978 merge_extras(&mut claims, extra, RESERVED_GENERIC_CLAIMS)?;
980 sign_jwt_claims(backend, signing_key_id, alg, claims).await
981}
982
983#[allow(clippy::too_many_arguments)] pub async fn mint_svid(
1002 backend: &dyn Backend,
1003 signing_key_id: &str,
1004 issuer_id: &str,
1005 alg: SvidAlg,
1006 subject_spiffe_id: &str,
1007 audience: &str,
1008 expires_in_secs: Option<u64>,
1009 extra: &Value,
1010) -> Result<String, GenericMintError> {
1011 let now = unix_now()?;
1012 let exp = expires_in_secs.map(|s| now.saturating_add(s));
1013
1014 let mut claims = serde_json::Map::new();
1015 claims.insert("iss".into(), Value::String(issuer_id.to_string()));
1016 claims.insert("sub".into(), Value::String(subject_spiffe_id.to_string()));
1017 claims.insert("aud".into(), Value::String(audience.to_string()));
1018 claims.insert("iat".into(), Value::Number(now.into()));
1019 if let Some(exp) = exp {
1020 claims.insert("exp".into(), Value::Number(exp.into()));
1021 }
1022 claims.insert("jti".into(), Value::String(jti_for(&claims)?));
1023
1024 merge_extras(&mut claims, extra, RESERVED_SVID_CLAIMS)?;
1027 sign_jwt_claims(backend, signing_key_id, alg, claims).await
1028}
1029
1030fn merge_extras(
1035 claims: &mut serde_json::Map<String, Value>,
1036 extra: &Value,
1037 reserved: &[&str],
1038) -> Result<(), GenericMintError> {
1039 if let Some(obj) = extra.as_object() {
1040 for (k, v) in obj {
1041 if reserved.contains(&k.as_str()) {
1042 return Err(GenericMintError::Reserved(ReservedClaim(k.clone())));
1043 }
1044 claims.insert(k.clone(), v.clone());
1045 }
1046 Ok(())
1047 } else if extra.is_null() {
1048 Ok(())
1049 } else {
1050 Err(GenericMintError::Reserved(ReservedClaim(
1051 "<claims must be a JSON object>".into(),
1052 )))
1053 }
1054}
1055
1056fn jti_for(claims: &serde_json::Map<String, Value>) -> Result<String, BackendError> {
1059 use sha2::{Digest, Sha512_256};
1060 let bytes = serde_json::to_vec(claims)
1061 .map_err(|e| BackendError::Protocol(format!("serializing claims: {e}")))?;
1062 let mut hasher = Sha512_256::new();
1063 hasher.update(&bytes);
1064 Ok(URL_SAFE_NO_PAD.encode(hasher.finalize()))
1065}
1066
1067fn jws_alg_for_public_key(public_key: &[u8]) -> Result<SvidAlg, BackendError> {
1068 if public_key.len() == 32 {
1069 return Ok(SvidAlg::EdDsa);
1070 }
1071 if decode_p256_public_key(public_key).is_ok() {
1075 return Ok(SvidAlg::Es256);
1076 }
1077 if decode_p384_public_key(public_key).is_ok() {
1078 return Ok(SvidAlg::Es384);
1079 }
1080 if decode_rsa_public_key(public_key).is_ok() {
1081 return Ok(SvidAlg::Rs256);
1082 }
1083 Err(BackendError::Protocol(format!(
1084 "JWT issuer public key must be Ed25519 raw public bytes, ECDSA P-256/P-384 PEM/DER, or RSA PEM/DER, got {} bytes",
1085 public_key.len()
1086 )))
1087}
1088
1089async fn sign_jwt_claims(
1094 backend: &dyn Backend,
1095 signing_key_id: &str,
1096 alg: SvidAlg,
1097 claims: serde_json::Map<String, Value>,
1098) -> Result<String, GenericMintError> {
1099 let public_key = backend.public_key(signing_key_id).await?;
1104 let kid = jwt_svid_jwk_kid(&public_key, alg);
1105 let header_bytes = serde_json::to_vec(&json!({
1106 "typ": "JWT",
1107 "alg": alg.header_alg(),
1108 "kid": kid,
1109 }))
1110 .map_err(|e| BackendError::Protocol(format!("serializing JWS header: {e}")))?;
1111 let claims_bytes = serde_json::to_vec(&claims)
1112 .map_err(|e| BackendError::Protocol(format!("serializing claims: {e}")))?;
1113 let signing_input = format!(
1114 "{}.{}",
1115 URL_SAFE_NO_PAD.encode(&header_bytes),
1116 URL_SAFE_NO_PAD.encode(&claims_bytes)
1117 );
1118 let signature = backend
1119 .sign_with_options(signing_key_id, signing_input.as_bytes(), alg.sign_options())
1120 .await?;
1121 Ok(format!(
1122 "{signing_input}.{}",
1123 URL_SAFE_NO_PAD.encode(&signature)
1124 ))
1125}
1126
1127#[derive(Debug)]
1132pub enum GenericMintError {
1133 Reserved(ReservedClaim),
1135 Backend(BackendError),
1137}
1138
1139impl std::fmt::Display for GenericMintError {
1140 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1141 match self {
1142 Self::Reserved(e) => write!(f, "{e}"),
1143 Self::Backend(e) => write!(f, "{e}"),
1144 }
1145 }
1146}
1147impl std::error::Error for GenericMintError {}
1148impl From<BackendError> for GenericMintError {
1149 fn from(e: BackendError) -> Self {
1150 Self::Backend(e)
1151 }
1152}
1153
1154#[cfg(test)]
1155mod tests {
1156 use super::*;
1157 use async_trait::async_trait;
1158 use base64::engine::general_purpose::URL_SAFE_NO_PAD;
1159 use nkeys::KeyPair;
1160
1161 use crate::backend::NewKey;
1162 use basil_proto::KeyType;
1163
1164 struct KeyPairBackend(KeyPair);
1168
1169 #[async_trait]
1170 impl Backend for KeyPairBackend {
1171 fn kind(&self) -> &'static str {
1172 "nkey-test"
1173 }
1174 async fn new_key(&self, _key_type: KeyType) -> Result<NewKey, BackendError> {
1175 Err(BackendError::Unsupported("new_key"))
1176 }
1177 async fn public_key(&self, _key_id: &str) -> Result<Vec<u8>, BackendError> {
1178 let (_, raw) = basil_nats::decode_public(&self.0.public_key())
1180 .map_err(|e| BackendError::Protocol(e.to_string()))?;
1181 Ok(raw.to_vec())
1182 }
1183 async fn sign(&self, _key_id: &str, message: &[u8]) -> Result<Vec<u8>, BackendError> {
1184 self.0
1185 .sign(message)
1186 .map_err(|e| BackendError::Backend(e.to_string()))
1187 }
1188 async fn verify(
1189 &self,
1190 _key_id: &str,
1191 message: &[u8],
1192 signature: &[u8],
1193 ) -> Result<bool, BackendError> {
1194 Ok(self.0.verify(message, signature).is_ok())
1195 }
1196 }
1197
1198 fn parse_token(token: &str) -> (Value, Value, String, Vec<u8>) {
1201 let parts: Vec<&str> = token.split('.').collect();
1202 assert_eq!(parts.len(), 3, "compact JWS has 3 parts");
1203 let header: Value =
1204 serde_json::from_slice(&URL_SAFE_NO_PAD.decode(parts[0]).unwrap()).unwrap();
1205 let claims: Value =
1206 serde_json::from_slice(&URL_SAFE_NO_PAD.decode(parts[1]).unwrap()).unwrap();
1207 let signing_input = format!("{}.{}", parts[0], parts[1]);
1208 let sig = URL_SAFE_NO_PAD.decode(parts[2]).unwrap();
1209 (header, claims, signing_input, sig)
1210 }
1211
1212 fn jwks_kids(body: &[u8]) -> Vec<String> {
1214 let parsed: Value = serde_json::from_slice(body).expect("jwks json");
1215 parsed["keys"]
1216 .as_array()
1217 .expect("keys array")
1218 .iter()
1219 .filter_map(|k| k["kid"].as_str().map(str::to_string))
1220 .collect()
1221 }
1222
1223 fn ed_public(seed: u8) -> Vec<u8> {
1227 vec![seed; 32]
1228 }
1229
1230 #[test]
1233 fn grace_window_includes_floor_and_latest_excludes_outside() {
1234 let versions = std::collections::BTreeMap::from([
1236 (1u32, ed_public(1)),
1237 (2, ed_public(2)),
1238 (3, ed_public(3)),
1239 (4, ed_public(4)),
1240 ]);
1241 let body = jwt_svid_jwks_grace_window(&versions, 3, 2, SvidAlg::EdDsa).expect("jwks");
1242 let kids = jwks_kids(&body);
1243 assert_eq!(kids.len(), 2, "only the in-window versions: {kids:?}");
1246 assert!(kids.contains(&jwt_svid_jwk_kid(&ed_public(2), SvidAlg::EdDsa)));
1247 assert!(kids.contains(&jwt_svid_jwk_kid(&ed_public(3), SvidAlg::EdDsa)));
1248 assert!(!kids.contains(&jwt_svid_jwk_kid(&ed_public(1), SvidAlg::EdDsa)));
1249 assert!(!kids.contains(&jwt_svid_jwk_kid(&ed_public(4), SvidAlg::EdDsa)));
1250 }
1251
1252 #[test]
1253 fn grace_window_floor_equals_latest_yields_single_key() {
1254 let versions = std::collections::BTreeMap::from([
1255 (1u32, ed_public(1)),
1256 (2, ed_public(2)),
1257 (3, ed_public(3)),
1258 ]);
1259 let body = jwt_svid_jwks_grace_window(&versions, 3, 3, SvidAlg::EdDsa).expect("jwks");
1261 let kids = jwks_kids(&body);
1262 assert_eq!(kids, vec![jwt_svid_jwk_kid(&ed_public(3), SvidAlg::EdDsa)]);
1263 }
1264
1265 #[test]
1266 fn grace_window_empty_version_map_yields_empty_set_no_panic() {
1267 let versions: std::collections::BTreeMap<u32, Vec<u8>> = std::collections::BTreeMap::new();
1268 let body = jwt_svid_jwks_grace_window(&versions, 1, 1, SvidAlg::EdDsa).expect("jwks");
1269 assert!(jwks_kids(&body).is_empty(), "no versions -> empty key set");
1270 }
1271
1272 #[test]
1273 fn grace_window_floor_zero_publishes_every_version_up_to_latest() {
1274 let versions = std::collections::BTreeMap::from([
1278 (1u32, ed_public(1)),
1279 (2, ed_public(2)),
1280 (3, ed_public(3)),
1281 (4, ed_public(4)),
1282 ]);
1283 let body = jwt_svid_jwks_grace_window(&versions, 3, 0, SvidAlg::EdDsa).expect("jwks");
1284 let kids = jwks_kids(&body);
1285 assert_eq!(kids.len(), 3, "floor 0 -> v1..=v3, v4 excluded: {kids:?}");
1286 assert!(!kids.contains(&jwt_svid_jwk_kid(&ed_public(4), SvidAlg::EdDsa)));
1287 }
1288
1289 #[test]
1290 fn grace_window_dedups_identical_public_keys_by_kid() {
1291 let shared = ed_public(7);
1294 let versions =
1295 std::collections::BTreeMap::from([(1u32, shared.clone()), (2, shared.clone())]);
1296 let body = jwt_svid_jwks_grace_window(&versions, 2, 1, SvidAlg::EdDsa).expect("jwks");
1297 let kids = jwks_kids(&body);
1298 assert_eq!(
1299 kids,
1300 vec![jwt_svid_jwk_kid(&shared, SvidAlg::EdDsa)],
1301 "identical public bytes dedup to one kid"
1302 );
1303 }
1304
1305 #[tokio::test]
1306 async fn jwt_svid_jwks_publishes_eddsa_issuer_key() {
1307 let kp = KeyPair::new_account();
1308 let backend = KeyPairBackend(KeyPair::from_seed(&kp.seed().unwrap()).unwrap());
1309
1310 let bytes = jwt_svid_jwks(&backend, "issuer-key", SvidAlg::EdDsa)
1311 .await
1312 .expect("jwks");
1313 let jwks: Value = serde_json::from_slice(&bytes).expect("jwks json");
1314 let key = jwks["keys"].as_array().unwrap().first().unwrap();
1315
1316 let (_, raw_public) = basil_nats::decode_public(&kp.public_key()).expect("public key");
1317 assert_eq!(key["kty"], "OKP");
1318 assert_eq!(key["crv"], "Ed25519");
1319 assert_eq!(key["alg"], "EdDSA");
1320 assert_eq!(key["use"], "sig");
1321 assert_eq!(key["x"], URL_SAFE_NO_PAD.encode(raw_public.as_slice()));
1322 assert_eq!(key["kid"], jwt_svid_jwk_kid(&raw_public, SvidAlg::EdDsa));
1323 }
1324
1325 #[test]
1326 fn jwt_svid_jwks_publishes_rs256_issuer_key() {
1327 use rsa::pkcs8::EncodePublicKey;
1328
1329 let mut rng = rand::thread_rng();
1330 let private = rsa::RsaPrivateKey::new(&mut rng, 2048).expect("rsa keygen");
1331 let public = rsa::RsaPublicKey::from(&private);
1332 let public_der = public.to_public_key_der().expect("public der");
1333
1334 let bytes =
1335 jwt_svid_jwks_from_public_key(public_der.as_bytes(), SvidAlg::Rs256).expect("jwks");
1336 let jwks: Value = serde_json::from_slice(&bytes).expect("jwks json");
1337 let key = jwks["keys"].as_array().unwrap().first().unwrap();
1338
1339 assert_eq!(key["kty"], "RSA");
1340 assert_eq!(key["alg"], "RS256");
1341 assert_eq!(key["use"], "sig");
1342 assert_eq!(key["n"], URL_SAFE_NO_PAD.encode(public.n().to_bytes_be()));
1343 assert_eq!(key["e"], URL_SAFE_NO_PAD.encode(public.e().to_bytes_be()));
1344 assert_eq!(
1345 key["kid"],
1346 jwt_svid_jwk_kid(public_der.as_bytes(), SvidAlg::Rs256)
1347 );
1348 }
1349
1350 #[test]
1351 fn jwt_svid_jwks_publishes_es256_issuer_key() {
1352 use p256::pkcs8::EncodePublicKey as _;
1353
1354 let signing_key = p256::ecdsa::SigningKey::random(&mut rand::thread_rng());
1355 let public = signing_key.verifying_key();
1356 let public_der = public.to_public_key_der().expect("public der");
1357 let encoded = public.to_encoded_point(false);
1358 let x = encoded.x().expect("x coordinate");
1359 let y = encoded.y().expect("y coordinate");
1360
1361 let bytes =
1362 jwt_svid_jwks_from_public_key(public_der.as_bytes(), SvidAlg::Es256).expect("jwks");
1363 let jwks: Value = serde_json::from_slice(&bytes).expect("jwks json");
1364 let key = jwks["keys"].as_array().unwrap().first().unwrap();
1365
1366 assert_eq!(key["kty"], "EC");
1367 assert_eq!(key["crv"], "P-256");
1368 assert_eq!(key["alg"], "ES256");
1369 assert_eq!(key["use"], "sig");
1370 assert_eq!(key["x"], URL_SAFE_NO_PAD.encode(x));
1371 assert_eq!(key["y"], URL_SAFE_NO_PAD.encode(y));
1372 assert_eq!(
1373 key["kid"],
1374 jwt_svid_jwk_kid(public_der.as_bytes(), SvidAlg::Es256)
1375 );
1376 }
1377
1378 #[test]
1379 fn jwt_svid_jwks_publishes_es384_issuer_key() {
1380 use p384::pkcs8::EncodePublicKey as _;
1381
1382 let signing_key = p384::ecdsa::SigningKey::random(&mut rand::thread_rng());
1383 let public = signing_key.verifying_key();
1384 let public_der = public.to_public_key_der().expect("public der");
1385 let encoded = public.to_encoded_point(false);
1386 let x = encoded.x().expect("x coordinate");
1387 let y = encoded.y().expect("y coordinate");
1388
1389 let bytes =
1390 jwt_svid_jwks_from_public_key(public_der.as_bytes(), SvidAlg::Es384).expect("jwks");
1391 let jwks: Value = serde_json::from_slice(&bytes).expect("jwks json");
1392 let key = jwks["keys"].as_array().unwrap().first().unwrap();
1393
1394 assert_eq!(key["kty"], "EC");
1395 assert_eq!(key["crv"], "P-384");
1396 assert_eq!(key["alg"], "ES384");
1397 assert_eq!(key["use"], "sig");
1398 assert_eq!(key["x"], URL_SAFE_NO_PAD.encode(x));
1399 assert_eq!(key["y"], URL_SAFE_NO_PAD.encode(y));
1400 assert_eq!(
1401 key["kid"],
1402 jwt_svid_jwk_kid(public_der.as_bytes(), SvidAlg::Es384)
1403 );
1404 }
1405
1406 #[tokio::test]
1407 async fn generic_mint_round_trips_and_verifies_under_issuer_key() {
1408 let kp = KeyPair::new_account();
1409 let backend = KeyPairBackend(KeyPair::from_seed(&kp.seed().unwrap()).unwrap());
1410
1411 let token = mint_generic(
1412 &backend,
1413 "issuer-key",
1414 "spire.issuer",
1415 "spiffe://example/sa/web",
1416 Some(60),
1417 &serde_json::json!({ "aud": "api", "role": "reader" }),
1418 )
1419 .await
1420 .expect("mint");
1421
1422 let (header, claims, signing_input, sig) = parse_token(&token);
1423 assert_eq!(header["typ"], "JWT");
1424 assert_eq!(header["alg"], "EdDSA");
1425 assert_eq!(claims["iss"], "spire.issuer");
1426 assert_eq!(claims["sub"], "spiffe://example/sa/web");
1427 assert_eq!(claims["aud"], "api");
1428 assert_eq!(claims["role"], "reader");
1429 assert!(claims["exp"].is_number());
1430 assert!(claims["jti"].is_string());
1431 kp.verify(signing_input.as_bytes(), &sig)
1433 .expect("signature verifies");
1434 }
1435
1436 #[tokio::test]
1437 async fn generic_mint_rs256_derives_alg_and_verifies_under_issuer_key() {
1438 let (backend, public_der, public_pem) = rsa_backend();
1439
1440 let token = mint_generic(
1441 &backend,
1442 "rsa-issuer",
1443 "spire.issuer",
1444 "spiffe://example.org/api",
1445 Some(120),
1446 &serde_json::json!({ "aud": "api" }),
1447 )
1448 .await
1449 .expect("mint generic rs256");
1450
1451 let parts: Vec<&str> = token.split('.').collect();
1452 assert_eq!(parts.len(), 3);
1453 let (header, claims, _signing_input, _sig) = parse_token(&token);
1454 assert_eq!(header["typ"], "JWT");
1455 assert_eq!(header["alg"], "RS256");
1456 assert_eq!(header["kid"], jwt_svid_jwk_kid(&public_der, SvidAlg::Rs256));
1457 assert_eq!(claims["iss"], "spire.issuer");
1458 assert_eq!(claims["sub"], "spiffe://example.org/api");
1459 assert_eq!(claims["aud"], "api");
1460
1461 let signing_input = format!("{}.{}", parts[0], parts[1]);
1462 let decoding_key =
1463 jsonwebtoken::DecodingKey::from_rsa_pem(public_pem.as_bytes()).expect("decoding key");
1464 let valid = jsonwebtoken::crypto::verify(
1465 parts[2],
1466 signing_input.as_bytes(),
1467 &decoding_key,
1468 jsonwebtoken::Algorithm::RS256,
1469 )
1470 .expect("verify");
1471 assert!(valid, "generic RS256 signature verifies under issuer key");
1472 }
1473
1474 #[tokio::test]
1475 async fn generic_mint_es256_derives_alg_and_verifies_under_issuer_key() {
1476 let backend = p256_backend();
1477
1478 let token = mint_generic(
1479 &backend,
1480 "ecdsa-issuer",
1481 "spire.issuer",
1482 "spiffe://example.org/api",
1483 Some(120),
1484 &serde_json::json!({ "aud": "api" }),
1485 )
1486 .await
1487 .expect("mint generic es256");
1488
1489 let parts: Vec<&str> = token.split('.').collect();
1490 assert_eq!(parts.len(), 3);
1491 let (header, claims, _signing_input, sig) = parse_token(&token);
1492 assert_eq!(header["typ"], "JWT");
1493 assert_eq!(header["alg"], "ES256");
1494 assert_eq!(
1495 header["kid"],
1496 jwt_svid_jwk_kid(&backend.public_der, SvidAlg::Es256),
1497 "generic JWS kid matches the published JWKS key id"
1498 );
1499 assert_eq!(claims["iss"], "spire.issuer");
1500 assert_eq!(claims["sub"], "spiffe://example.org/api");
1501 assert_eq!(claims["aud"], "api");
1502 assert_eq!(sig.len(), 64, "ES256 signatures are raw fixed r||s");
1503
1504 let signing_input = format!("{}.{}", parts[0], parts[1]);
1505 let decoding_key = jsonwebtoken::DecodingKey::from_ec_pem(backend.public_pem.as_bytes())
1506 .expect("decoding key");
1507 let valid = jsonwebtoken::crypto::verify(
1508 parts[2],
1509 signing_input.as_bytes(),
1510 &decoding_key,
1511 jsonwebtoken::Algorithm::ES256,
1512 )
1513 .expect("verify");
1514 assert!(valid, "generic ES256 signature verifies under issuer key");
1515 }
1516
1517 #[tokio::test]
1518 async fn generic_mint_es384_derives_alg_and_verifies_under_issuer_key() {
1519 let backend = p384_backend();
1520
1521 let token = mint_generic(
1522 &backend,
1523 "ecdsa384-issuer",
1524 "spire.issuer",
1525 "spiffe://example.org/api",
1526 Some(120),
1527 &serde_json::Value::Null,
1528 )
1529 .await
1530 .expect("mint generic es384");
1531
1532 let parts: Vec<&str> = token.split('.').collect();
1533 assert_eq!(parts.len(), 3);
1534 let (header, _claims, _signing_input, sig) = parse_token(&token);
1535 assert_eq!(header["alg"], "ES384");
1536 assert_eq!(
1537 header["kid"],
1538 jwt_svid_jwk_kid(&backend.public_der, SvidAlg::Es384)
1539 );
1540 assert_eq!(sig.len(), 96, "ES384 signatures are raw fixed r||s");
1541
1542 let signing_input = format!("{}.{}", parts[0], parts[1]);
1543 let decoding_key = jsonwebtoken::DecodingKey::from_ec_pem(backend.public_pem.as_bytes())
1544 .expect("decoding key");
1545 let valid = jsonwebtoken::crypto::verify(
1546 parts[2],
1547 signing_input.as_bytes(),
1548 &decoding_key,
1549 jsonwebtoken::Algorithm::ES384,
1550 )
1551 .expect("verify");
1552 assert!(valid, "generic ES384 signature verifies under issuer key");
1553 }
1554
1555 #[tokio::test]
1556 async fn generic_mint_rejects_reserved_claim() {
1557 let backend = KeyPairBackend(KeyPair::new_account());
1558 for reserved in ["iss", "iat", "exp", "jti", "sub", "nbf"] {
1559 let err = mint_generic(
1560 &backend,
1561 "p",
1562 "issuer",
1563 "sub",
1564 None,
1565 &serde_json::json!({ reserved: "x" }),
1566 )
1567 .await
1568 .expect_err("reserved claim rejected");
1569 assert!(
1570 matches!(err, GenericMintError::Reserved(ReservedClaim(k)) if k == reserved),
1573 "expected reserved {reserved}"
1574 );
1575 }
1576 }
1577
1578 #[tokio::test]
1579 async fn generic_mint_without_ttl_omits_exp() {
1580 let backend = KeyPairBackend(KeyPair::new_account());
1581 let token = mint_generic(
1582 &backend,
1583 "p",
1584 "issuer",
1585 "sub",
1586 None,
1587 &serde_json::Value::Null,
1588 )
1589 .await
1590 .expect("mint");
1591 let (_, claims, _, _) = parse_token(&token);
1592 assert!(claims.get("exp").is_none(), "no ttl => no exp claim");
1593 }
1594
1595 #[tokio::test]
1596 async fn account_mint_iss_is_operator_nkey_and_verifies() {
1597 let operator = KeyPair::new_operator();
1599 let backend = KeyPairBackend(KeyPair::from_seed(&operator.seed().unwrap()).unwrap());
1600 let account = KeyPair::new_account();
1601
1602 let token = mint_nats_account(
1603 &backend,
1604 "operator-key",
1605 NkeyType::Operator,
1606 &account.public_key(),
1607 "acme",
1608 Some(3600),
1609 vec![KeyPair::new_account().public_key()],
1610 )
1611 .await
1612 .expect("mint");
1613
1614 let (_, claims, signing_input, sig) = parse_token(&token);
1615 assert_eq!(claims["nats"]["type"], "account");
1616 assert_eq!(claims["sub"], account.public_key());
1617 assert!(claims["iss"].as_str().unwrap().starts_with('O'));
1618 operator
1619 .verify(signing_input.as_bytes(), &sig)
1620 .expect("verifies under operator key");
1621 }
1622
1623 #[tokio::test]
1624 async fn user_mint_sets_issuer_account_for_signing_key() {
1625 let signing = KeyPair::new_account();
1629 let backend = KeyPairBackend(KeyPair::from_seed(&signing.seed().unwrap()).unwrap());
1630 let account_identity = KeyPair::new_account();
1631 let user = KeyPair::new_user();
1632
1633 let token = mint_nats_user(
1634 &backend,
1635 "account-signing-key",
1636 NkeyType::Account,
1637 &user.public_key(),
1638 Some(&account_identity.public_key()),
1639 "svc-user",
1640 Some(3600),
1641 basil_nats::UserPermissions::default(),
1642 )
1643 .await
1644 .expect("mint user");
1645
1646 let (_, claims, signing_input, sig) = parse_token(&token);
1647 assert_eq!(claims["nats"]["type"], "user");
1648 assert_eq!(
1649 claims["iss"],
1650 signing.public_key(),
1651 "iss is the signing key"
1652 );
1653 assert_eq!(
1654 claims["nats"]["issuer_account"],
1655 account_identity.public_key(),
1656 "issuer_account names the owning account identity"
1657 );
1658 signing
1659 .verify(signing_input.as_bytes(), &sig)
1660 .expect("verifies under the signing key");
1661 }
1662
1663 #[tokio::test]
1664 async fn user_mint_without_issuer_account_omits_the_claim() {
1665 let account = KeyPair::new_account();
1666 let backend = KeyPairBackend(KeyPair::from_seed(&account.seed().unwrap()).unwrap());
1667 let user = KeyPair::new_user();
1668
1669 let token = mint_nats_user(
1670 &backend,
1671 "account-key",
1672 NkeyType::Account,
1673 &user.public_key(),
1674 None,
1675 "svc-user",
1676 None,
1677 basil_nats::UserPermissions::default(),
1678 )
1679 .await
1680 .expect("mint user");
1681
1682 let (_, claims, _, _) = parse_token(&token);
1683 assert_eq!(claims["iss"], account.public_key());
1684 assert!(
1685 claims["nats"].get("issuer_account").is_none(),
1686 "no issuer_account claim when the account identity key signs"
1687 );
1688 }
1689
1690 #[tokio::test]
1691 async fn user_mint_rejects_malformed_issuer_account() {
1692 let backend = KeyPairBackend(KeyPair::new_account());
1693 let user = KeyPair::new_user();
1694 let err = mint_nats_user(
1695 &backend,
1696 "account-key",
1697 NkeyType::Account,
1698 &user.public_key(),
1699 Some("not-an-account-nkey"),
1700 "svc-user",
1701 None,
1702 basil_nats::UserPermissions::default(),
1703 )
1704 .await
1705 .expect_err("malformed issuer account rejected");
1706 assert!(matches!(err, BackendError::Protocol(_)));
1707 }
1708
1709 #[tokio::test]
1710 async fn sign_nats_jwt_derives_issuer_jti_and_verifies() {
1711 let account = KeyPair::new_account();
1712 let backend = KeyPairBackend(KeyPair::from_seed(&account.seed().unwrap()).unwrap());
1713 let user = KeyPair::new_user();
1714 let token = sign_nats_jwt(
1715 &backend,
1716 SignNatsJwtSpec {
1717 signing_key_id: "account-key",
1718 issuer_role: NkeyType::Account,
1719 claims: &serde_json::json!({
1720 "sub": user.public_key(),
1721 "name": "rich-user",
1722 "nats": { "type": "user", "version": 2 }
1723 }),
1724 expected_kind: Some(NatsJwtKind::User),
1725 issued_at: Some(1_700_000_000),
1726 expires_at: Some(1_700_003_600),
1727 jti_mode: NatsJtiMode::RequireValid,
1728 },
1729 )
1730 .await
1731 .expect("sign nats jwt");
1732
1733 let (header, claims, signing_input, sig) = parse_token(&token);
1734 assert_eq!(header["alg"], "ed25519-nkey");
1735 assert_eq!(claims["iss"], account.public_key());
1736 assert_eq!(claims["sub"], user.public_key());
1737 assert_eq!(claims["iat"], 1_700_000_000);
1738 assert_eq!(claims["exp"], 1_700_003_600);
1739 assert_eq!(claims["nats"]["type"], "user");
1740 assert!(claims["jti"].as_str().is_some_and(|jti| !jti.is_empty()));
1741 account
1742 .verify(signing_input.as_bytes(), &sig)
1743 .expect("verifies under account key");
1744 }
1745
1746 #[tokio::test]
1747 async fn sign_nats_jwt_allows_missing_name() {
1748 let account = KeyPair::new_account();
1749 let backend = KeyPairBackend(KeyPair::from_seed(&account.seed().unwrap()).unwrap());
1750 let user = KeyPair::new_user();
1751 let token = sign_nats_jwt(
1752 &backend,
1753 SignNatsJwtSpec {
1754 signing_key_id: "account-key",
1755 issuer_role: NkeyType::Account,
1756 claims: &serde_json::json!({
1757 "sub": user.public_key(),
1758 "nats": { "type": "user", "version": 2 }
1759 }),
1760 expected_kind: Some(NatsJwtKind::User),
1761 issued_at: Some(1_700_000_000),
1762 expires_at: None,
1763 jti_mode: NatsJtiMode::RequireValid,
1764 },
1765 )
1766 .await
1767 .expect("sign nats jwt without name");
1768
1769 let (_, claims, _, _) = parse_token(&token);
1770 assert!(claims.get("name").is_none());
1771 assert!(claims["jti"].as_str().is_some_and(|jti| !jti.is_empty()));
1772 }
1773
1774 #[tokio::test]
1775 async fn sign_nats_jwt_accepts_jti_with_aud_and_nbf() {
1776 let account = KeyPair::new_account();
1777 let backend = KeyPairBackend(KeyPair::from_seed(&account.seed().unwrap()).unwrap());
1778 let user = KeyPair::new_user();
1779 let issuer = account.public_key();
1780 let subject = user.public_key();
1781 let jti = basil_nats::jti_for_standard_claims(
1782 &issuer,
1783 &subject,
1784 Some("rich-user"),
1785 1_700_000_000,
1786 Some(1_700_003_600),
1787 Some("orders"),
1788 Some(1_699_999_900),
1789 )
1790 .expect("computed jti");
1791 let token = sign_nats_jwt(
1792 &backend,
1793 SignNatsJwtSpec {
1794 signing_key_id: "account-key",
1795 issuer_role: NkeyType::Account,
1796 claims: &serde_json::json!({
1797 "aud": "orders",
1798 "exp": 1_700_003_600_u64,
1799 "iat": 1_700_000_000_u64,
1800 "iss": issuer.clone(),
1801 "jti": jti.clone(),
1802 "name": "rich-user",
1803 "nbf": 1_699_999_900_u64,
1804 "sub": subject.clone(),
1805 "nats": { "type": "user", "version": 2 }
1806 }),
1807 expected_kind: Some(NatsJwtKind::User),
1808 issued_at: None,
1809 expires_at: None,
1810 jti_mode: NatsJtiMode::RequireValid,
1811 },
1812 )
1813 .await
1814 .expect("sign nats jwt with standard claims");
1815
1816 let (_, claims, _, _) = parse_token(&token);
1817 assert_eq!(claims["jti"], jti);
1818 assert_eq!(claims["aud"], "orders");
1819 assert_eq!(claims["nbf"], 1_699_999_900_u64);
1820 }
1821
1822 #[tokio::test]
1823 async fn sign_nats_jwt_rejects_mismatched_issuer() {
1824 let backend = KeyPairBackend(KeyPair::new_account());
1825 let user = KeyPair::new_user();
1826 let err = sign_nats_jwt(
1827 &backend,
1828 SignNatsJwtSpec {
1829 signing_key_id: "account-key",
1830 issuer_role: NkeyType::Account,
1831 claims: &serde_json::json!({
1832 "iss": KeyPair::new_account().public_key(),
1833 "sub": user.public_key(),
1834 "name": "bad-iss",
1835 "nats": { "type": "user", "version": 2 }
1836 }),
1837 expected_kind: Some(NatsJwtKind::User),
1838 issued_at: None,
1839 expires_at: None,
1840 jti_mode: NatsJtiMode::RequireValid,
1841 },
1842 )
1843 .await
1844 .expect_err("mismatched issuer rejects");
1845 assert!(matches!(
1846 err,
1847 BackendError::Protocol(message)
1848 if message.starts_with("invalid nats jwt iss")
1849 ));
1850 }
1851
1852 #[tokio::test]
1853 async fn account_mint_rejects_bad_subject_nkey() {
1854 let backend = KeyPairBackend(KeyPair::new_operator());
1855 let err = mint_nats_account(
1856 &backend,
1857 "p",
1858 NkeyType::Operator,
1859 "not-an-nkey",
1860 "n",
1861 None,
1862 vec![],
1863 )
1864 .await
1865 .expect_err("bad subject");
1866 assert!(matches!(err, BackendError::Protocol(_)));
1867 }
1868
1869 #[tokio::test]
1870 async fn account_mint_rejects_disallowed_issuer_role() {
1871 let backend = KeyPairBackend(KeyPair::new_user());
1874 let subject = KeyPair::new_account().public_key();
1875 let err = mint_nats_account(&backend, "p", NkeyType::User, &subject, "n", None, vec![])
1876 .await
1877 .expect_err("disallowed issuer role");
1878 assert!(matches!(err, BackendError::Protocol(_)));
1879 }
1880
1881 #[tokio::test]
1882 async fn operator_mint_self_signs_and_verifies() {
1883 let operator = KeyPair::new_operator();
1884 let backend = KeyPairBackend(KeyPair::from_seed(&operator.seed().unwrap()).unwrap());
1885
1886 let token = mint_nats_operator(
1887 &backend,
1888 "operator-key",
1889 NkeyType::Operator,
1890 None, "root-op",
1892 None,
1893 vec![],
1894 "nats://localhost:4222".into(),
1895 KeyPair::new_account().public_key(),
1896 )
1897 .await
1898 .expect("mint");
1899
1900 let (_, claims, signing_input, sig) = parse_token(&token);
1901 assert_eq!(claims["nats"]["type"], "operator");
1902 let iss = claims["iss"].as_str().unwrap();
1903 assert!(iss.starts_with('O'));
1904 assert_eq!(claims["sub"], iss, "self-signed: sub == iss");
1905 assert_eq!(
1906 claims["nats"]["account_server_url"],
1907 "nats://localhost:4222"
1908 );
1909 assert!(claims.get("exp").is_none(), "no ttl => no exp");
1910 operator
1911 .verify(signing_input.as_bytes(), &sig)
1912 .expect("verifies under operator key");
1913 }
1914
1915 #[tokio::test]
1916 async fn signer_mint_requires_matching_account_or_operator_subject() {
1917 let account = KeyPair::new_account();
1918 let backend = KeyPairBackend(KeyPair::from_seed(&account.seed().unwrap()).unwrap());
1919 let signing = KeyPair::new_account();
1920
1921 let token = mint_nats_signer(
1922 &backend,
1923 "account-key",
1924 NkeyType::Account,
1925 &signing.public_key(),
1926 "account-signer",
1927 Some(60),
1928 )
1929 .await
1930 .expect("mint");
1931
1932 let (_, claims, signing_input, sig) = parse_token(&token);
1933 assert_eq!(claims["nats"]["type"], "signer");
1934 assert_eq!(claims["sub"], signing.public_key());
1935 assert!(claims["iss"].as_str().unwrap().starts_with('A'));
1936 account
1937 .verify(signing_input.as_bytes(), &sig)
1938 .expect("verifies under account key");
1939
1940 let err = mint_nats_signer(
1941 &backend,
1942 "account-key",
1943 NkeyType::Account,
1944 &KeyPair::new_operator().public_key(),
1945 "wrong-signer",
1946 None,
1947 )
1948 .await
1949 .expect_err("wrong subject role");
1950 assert!(matches!(err, BackendError::Protocol(_)));
1951 }
1952
1953 #[tokio::test]
1954 async fn server_and_curve_mints_verify_expected_prefixes() {
1955 let server = KeyPair::new_server();
1956 let server_backend = KeyPairBackend(KeyPair::from_seed(&server.seed().unwrap()).unwrap());
1957
1958 let server_token = mint_nats_server(
1959 &server_backend,
1960 "server-key",
1961 NkeyType::Server,
1962 &server.public_key(),
1963 "nats-server",
1964 None,
1965 )
1966 .await
1967 .expect("server mint");
1968 let (_, claims, signing_input, sig) = parse_token(&server_token);
1969 assert_eq!(claims["nats"]["type"], "server");
1970 assert!(claims["iss"].as_str().unwrap().starts_with('N'));
1971 assert!(claims["sub"].as_str().unwrap().starts_with('N'));
1972 server
1973 .verify(signing_input.as_bytes(), &sig)
1974 .expect("verifies under server key");
1975
1976 let curve_source = KeyPair::new_account();
1977 let curve_backend =
1978 KeyPairBackend(KeyPair::from_seed(&curve_source.seed().unwrap()).unwrap());
1979 let (_, raw_curve) = basil_nats::decode_public(&curve_source.public_key())
1980 .expect("curve source public decodes");
1981 let curve_public =
1982 basil_nats::encode_public(NkeyType::Curve, &raw_curve).expect("curve nkey encodes");
1983 let curve_token = mint_nats_curve(
1984 &curve_backend,
1985 "curve-key",
1986 NkeyType::Curve,
1987 &curve_public,
1988 "curve-key",
1989 None,
1990 )
1991 .await
1992 .expect("curve mint");
1993 let (_, claims, signing_input, sig) = parse_token(&curve_token);
1994 assert_eq!(claims["nats"]["type"], "curve");
1995 assert!(claims["iss"].as_str().unwrap().starts_with('X'));
1996 assert!(claims["sub"].as_str().unwrap().starts_with('X'));
1997 curve_source
1998 .verify(signing_input.as_bytes(), &sig)
1999 .expect("verifies under encoded curve issuer bytes");
2000 }
2001
2002 #[tokio::test]
2003 async fn unsupported_issuer_role_is_protocol_error() {
2004 let backend = KeyPairBackend(KeyPair::new_operator());
2005 let server = KeyPair::new_server();
2006 let err = mint_nats_server(
2007 &backend,
2008 "operator-key",
2009 NkeyType::Operator,
2010 &server.public_key(),
2011 "n",
2012 None,
2013 )
2014 .await
2015 .expect_err("operator cannot mint server role");
2016 assert!(matches!(
2017 err,
2018 BackendError::Protocol(message) if message.starts_with("unsupported issuer role")
2019 ));
2020 }
2021
2022 #[tokio::test]
2025 async fn svid_eddsa_mints_a_verifiable_jwt_svid() {
2026 let kp = KeyPair::new_account();
2027 let backend = KeyPairBackend(KeyPair::from_seed(&kp.seed().unwrap()).unwrap());
2028
2029 let token = mint_svid(
2030 &backend,
2031 "issuer-key",
2032 "spire.svid_issuer",
2033 SvidAlg::EdDsa,
2034 "spiffe://example.org/web-01",
2035 "vault",
2036 Some(300),
2037 &serde_json::json!({ "role": "ingest" }),
2038 )
2039 .await
2040 .expect("mint svid");
2041
2042 let (header, claims, signing_input, sig) = parse_token(&token);
2043 assert_eq!(header["typ"], "JWT");
2044 assert_eq!(header["alg"], "EdDSA");
2045 assert_eq!(claims["iss"], "spire.svid_issuer");
2046 assert_eq!(claims["sub"], "spiffe://example.org/web-01");
2047 assert_eq!(claims["aud"], "vault", "aud is required + preset-owned");
2048 assert!(claims["iat"].is_number());
2049 assert!(claims["exp"].is_number());
2050 assert!(claims["jti"].is_string());
2051 assert_eq!(claims["role"], "ingest", "non-reserved extra merged");
2052 kp.verify(signing_input.as_bytes(), &sig)
2054 .expect("svid signature verifies under issuer key");
2055 }
2056
2057 #[tokio::test]
2058 async fn svid_without_ttl_omits_exp() {
2059 let backend = KeyPairBackend(KeyPair::new_account());
2060 let token = mint_svid(
2061 &backend,
2062 "k",
2063 "iss",
2064 SvidAlg::EdDsa,
2065 "spiffe://example.org/svc",
2066 "aud",
2067 None,
2068 &serde_json::Value::Null,
2069 )
2070 .await
2071 .expect("mint");
2072 let (_, claims, _, _) = parse_token(&token);
2073 assert!(claims.get("exp").is_none(), "no ttl => no exp");
2074 }
2075
2076 #[tokio::test]
2077 async fn svid_rejects_reserved_claims_including_aud() {
2078 let backend = KeyPairBackend(KeyPair::new_account());
2079 for reserved in ["iss", "iat", "exp", "jti", "sub", "nbf", "aud"] {
2082 let err = mint_svid(
2083 &backend,
2084 "k",
2085 "iss",
2086 SvidAlg::EdDsa,
2087 "spiffe://example.org/svc",
2088 "aud",
2089 None,
2090 &serde_json::json!({ reserved: "x" }),
2091 )
2092 .await
2093 .expect_err("reserved claim rejected");
2094 assert!(
2095 matches!(err, GenericMintError::Reserved(ReservedClaim(k)) if k == reserved),
2098 "expected reserved {reserved}"
2099 );
2100 }
2101 }
2102
2103 struct RsaBackend {
2108 encoding_key: jsonwebtoken::EncodingKey,
2109 public_der: Vec<u8>,
2112 }
2113
2114 #[async_trait]
2115 impl Backend for RsaBackend {
2116 fn kind(&self) -> &'static str {
2117 "rsa-test"
2118 }
2119 async fn new_key(&self, _key_type: KeyType) -> Result<NewKey, BackendError> {
2120 Err(BackendError::Unsupported("new_key"))
2121 }
2122 async fn public_key(&self, _key_id: &str) -> Result<Vec<u8>, BackendError> {
2123 Ok(self.public_der.clone())
2124 }
2125 async fn sign(&self, _key_id: &str, input: &[u8]) -> Result<Vec<u8>, BackendError> {
2126 self.rs256_sign(input)
2127 }
2128 async fn sign_with_options(
2129 &self,
2130 _key_id: &str,
2131 input: &[u8],
2132 options: SignOptions,
2133 ) -> Result<Vec<u8>, BackendError> {
2134 if options != SignOptions::Rs256Pkcs1v15Sha256 {
2135 return Err(BackendError::Unsupported("rsa test sign options"));
2136 }
2137 self.rs256_sign(input)
2138 }
2139 async fn verify(
2140 &self,
2141 _key_id: &str,
2142 _message: &[u8],
2143 _signature: &[u8],
2144 ) -> Result<bool, BackendError> {
2145 Err(BackendError::Unsupported("verify"))
2146 }
2147 }
2148
2149 impl RsaBackend {
2150 fn rs256_sign(&self, input: &[u8]) -> Result<Vec<u8>, BackendError> {
2151 let b64 = jsonwebtoken::crypto::sign(
2154 input,
2155 &self.encoding_key,
2156 jsonwebtoken::Algorithm::RS256,
2157 )
2158 .map_err(|e| BackendError::Backend(e.to_string()))?;
2159 URL_SAFE_NO_PAD
2160 .decode(b64)
2161 .map_err(|e| BackendError::Backend(e.to_string()))
2162 }
2163 }
2164
2165 fn rsa_backend() -> (RsaBackend, Vec<u8>, String) {
2166 use rsa::pkcs1::EncodeRsaPrivateKey;
2167 use rsa::pkcs8::{EncodePublicKey, LineEnding};
2168
2169 let mut rng = rand::thread_rng();
2171 let private = rsa::RsaPrivateKey::new(&mut rng, 2048).expect("rsa keygen");
2172 let public = rsa::RsaPublicKey::from(&private);
2173 let private_pem = private.to_pkcs1_pem(LineEnding::LF).expect("pkcs1 pem");
2174 let public_pem = public.to_public_key_pem(LineEnding::LF).expect("spki pem");
2175 let public_der = public
2176 .to_public_key_der()
2177 .expect("spki der")
2178 .as_bytes()
2179 .to_vec();
2180 let backend = RsaBackend {
2181 encoding_key: jsonwebtoken::EncodingKey::from_rsa_pem(private_pem.as_bytes())
2182 .expect("encoding key"),
2183 public_der: public_der.clone(),
2184 };
2185 (backend, public_der, public_pem)
2186 }
2187
2188 struct P256Backend {
2192 encoding_key: jsonwebtoken::EncodingKey,
2193 public_der: Vec<u8>,
2194 public_pem: String,
2195 }
2196
2197 #[async_trait]
2198 impl Backend for P256Backend {
2199 fn kind(&self) -> &'static str {
2200 "p256-test"
2201 }
2202 async fn new_key(&self, _key_type: KeyType) -> Result<NewKey, BackendError> {
2203 Err(BackendError::Unsupported("new_key"))
2204 }
2205 async fn public_key(&self, _key_id: &str) -> Result<Vec<u8>, BackendError> {
2206 Ok(self.public_der.clone())
2207 }
2208 async fn sign(&self, key_id: &str, input: &[u8]) -> Result<Vec<u8>, BackendError> {
2209 self.sign_with_options(key_id, input, SignOptions::Es256)
2210 .await
2211 }
2212 async fn sign_with_options(
2213 &self,
2214 _key_id: &str,
2215 input: &[u8],
2216 options: SignOptions,
2217 ) -> Result<Vec<u8>, BackendError> {
2218 if options != SignOptions::Es256 {
2219 return Err(BackendError::Unsupported("p256 test sign options"));
2220 }
2221 let b64 = jsonwebtoken::crypto::sign(
2222 input,
2223 &self.encoding_key,
2224 jsonwebtoken::Algorithm::ES256,
2225 )
2226 .map_err(|e| BackendError::Backend(e.to_string()))?;
2227 URL_SAFE_NO_PAD
2228 .decode(b64)
2229 .map_err(|e| BackendError::Backend(e.to_string()))
2230 }
2231 async fn verify(
2232 &self,
2233 _key_id: &str,
2234 _message: &[u8],
2235 _signature: &[u8],
2236 ) -> Result<bool, BackendError> {
2237 Err(BackendError::Unsupported("verify"))
2238 }
2239 }
2240
2241 fn p256_backend() -> P256Backend {
2242 use p256::pkcs8::{EncodePrivateKey as _, EncodePublicKey as _, LineEnding};
2243
2244 let signing_key = p256::ecdsa::SigningKey::random(&mut rand::thread_rng());
2245 let private_der = signing_key.to_pkcs8_der().expect("pkcs8 der");
2246 let public = signing_key.verifying_key();
2247 let public_der = public
2248 .to_public_key_der()
2249 .expect("spki der")
2250 .as_bytes()
2251 .to_vec();
2252 let public_pem = public.to_public_key_pem(LineEnding::LF).expect("spki pem");
2253 P256Backend {
2254 encoding_key: jsonwebtoken::EncodingKey::from_ec_der(private_der.as_bytes()),
2255 public_der,
2256 public_pem,
2257 }
2258 }
2259
2260 struct P384Backend {
2262 encoding_key: jsonwebtoken::EncodingKey,
2263 public_der: Vec<u8>,
2264 public_pem: String,
2265 }
2266
2267 #[async_trait]
2268 impl Backend for P384Backend {
2269 fn kind(&self) -> &'static str {
2270 "p384-test"
2271 }
2272 async fn new_key(&self, _key_type: KeyType) -> Result<NewKey, BackendError> {
2273 Err(BackendError::Unsupported("new_key"))
2274 }
2275 async fn public_key(&self, _key_id: &str) -> Result<Vec<u8>, BackendError> {
2276 Ok(self.public_der.clone())
2277 }
2278 async fn sign(&self, key_id: &str, input: &[u8]) -> Result<Vec<u8>, BackendError> {
2279 self.sign_with_options(key_id, input, SignOptions::Es384)
2280 .await
2281 }
2282 async fn sign_with_options(
2283 &self,
2284 _key_id: &str,
2285 input: &[u8],
2286 options: SignOptions,
2287 ) -> Result<Vec<u8>, BackendError> {
2288 if options != SignOptions::Es384 {
2289 return Err(BackendError::Unsupported("p384 test sign options"));
2290 }
2291 let b64 = jsonwebtoken::crypto::sign(
2292 input,
2293 &self.encoding_key,
2294 jsonwebtoken::Algorithm::ES384,
2295 )
2296 .map_err(|e| BackendError::Backend(e.to_string()))?;
2297 URL_SAFE_NO_PAD
2298 .decode(b64)
2299 .map_err(|e| BackendError::Backend(e.to_string()))
2300 }
2301 async fn verify(
2302 &self,
2303 _key_id: &str,
2304 _message: &[u8],
2305 _signature: &[u8],
2306 ) -> Result<bool, BackendError> {
2307 Err(BackendError::Unsupported("verify"))
2308 }
2309 }
2310
2311 fn p384_backend() -> P384Backend {
2312 use p384::pkcs8::{EncodePrivateKey as _, EncodePublicKey as _, LineEnding};
2313
2314 let signing_key = p384::ecdsa::SigningKey::random(&mut rand::thread_rng());
2315 let private_der = signing_key.to_pkcs8_der().expect("pkcs8 der");
2316 let public = signing_key.verifying_key();
2317 let public_der = public
2318 .to_public_key_der()
2319 .expect("spki der")
2320 .as_bytes()
2321 .to_vec();
2322 let public_pem = public.to_public_key_pem(LineEnding::LF).expect("spki pem");
2323 P384Backend {
2324 encoding_key: jsonwebtoken::EncodingKey::from_ec_der(private_der.as_bytes()),
2325 public_der,
2326 public_pem,
2327 }
2328 }
2329
2330 #[tokio::test]
2331 async fn svid_rs256_mints_a_verifiable_jwt_svid() {
2332 let (backend, public_der, public_pem) = rsa_backend();
2333
2334 let token = mint_svid(
2335 &backend,
2336 "rsa-issuer",
2337 "spiffe://example.org",
2338 SvidAlg::Rs256,
2339 "spiffe://example.org/db-01",
2340 "vault",
2341 Some(300),
2342 &serde_json::Value::Null,
2343 )
2344 .await
2345 .expect("mint rs256 svid");
2346
2347 let parts: Vec<&str> = token.split('.').collect();
2348 assert_eq!(parts.len(), 3);
2349 let (header, claims, _signing_input, _sig) = parse_token(&token);
2350 assert_eq!(header["alg"], "RS256");
2351 assert_eq!(claims["sub"], "spiffe://example.org/db-01");
2352 assert_eq!(
2356 header["kid"],
2357 jwt_svid_jwk_kid(&public_der, SvidAlg::Rs256),
2358 "JWS kid matches the published JWKS key id"
2359 );
2360 let signing_input = format!("{}.{}", parts[0], parts[1]);
2362 let decoding_key =
2363 jsonwebtoken::DecodingKey::from_rsa_pem(public_pem.as_bytes()).expect("decoding key");
2364 let valid = jsonwebtoken::crypto::verify(
2365 parts[2],
2366 signing_input.as_bytes(),
2367 &decoding_key,
2368 jsonwebtoken::Algorithm::RS256,
2369 )
2370 .expect("verify");
2371 assert!(valid, "RS256 signature verifies under issuer key");
2372 }
2373
2374 #[tokio::test]
2375 async fn svid_es256_mints_a_verifiable_jwt_svid() {
2376 let backend = p256_backend();
2377
2378 let token = mint_svid(
2379 &backend,
2380 "ecdsa-issuer",
2381 "spiffe://example.org",
2382 SvidAlg::Es256,
2383 "spiffe://example.org/db-01",
2384 "vault",
2385 Some(300),
2386 &serde_json::Value::Null,
2387 )
2388 .await
2389 .expect("mint es256 svid");
2390
2391 let parts: Vec<&str> = token.split('.').collect();
2392 assert_eq!(parts.len(), 3);
2393 let (header, claims, _signing_input, sig) = parse_token(&token);
2394 assert_eq!(header["alg"], "ES256");
2395 assert_eq!(claims["sub"], "spiffe://example.org/db-01");
2396 assert_eq!(
2397 header["kid"],
2398 jwt_svid_jwk_kid(&backend.public_der, SvidAlg::Es256),
2399 "JWS kid matches the published JWKS key id"
2400 );
2401 assert_eq!(sig.len(), 64, "ES256 signatures are raw fixed r||s");
2402
2403 let signing_input = format!("{}.{}", parts[0], parts[1]);
2404 let decoding_key = jsonwebtoken::DecodingKey::from_ec_pem(backend.public_pem.as_bytes())
2405 .expect("decoding key");
2406 let valid = jsonwebtoken::crypto::verify(
2407 parts[2],
2408 signing_input.as_bytes(),
2409 &decoding_key,
2410 jsonwebtoken::Algorithm::ES256,
2411 )
2412 .expect("verify");
2413 assert!(valid, "ES256 signature verifies under issuer key");
2414 }
2415
2416 #[tokio::test]
2417 async fn svid_es384_mints_a_verifiable_jwt_svid() {
2418 let backend = p384_backend();
2419
2420 let token = mint_svid(
2421 &backend,
2422 "ecdsa384-issuer",
2423 "spiffe://example.org",
2424 SvidAlg::Es384,
2425 "spiffe://example.org/db-01",
2426 "vault",
2427 Some(300),
2428 &serde_json::Value::Null,
2429 )
2430 .await
2431 .expect("mint es384 svid");
2432
2433 let parts: Vec<&str> = token.split('.').collect();
2434 assert_eq!(parts.len(), 3);
2435 let (header, claims, _signing_input, sig) = parse_token(&token);
2436 assert_eq!(header["alg"], "ES384");
2437 assert_eq!(claims["sub"], "spiffe://example.org/db-01");
2438 assert_eq!(
2439 header["kid"],
2440 jwt_svid_jwk_kid(&backend.public_der, SvidAlg::Es384),
2441 "JWS kid matches the published JWKS key id"
2442 );
2443 assert_eq!(sig.len(), 96, "ES384 signatures are raw fixed r||s");
2444
2445 let signing_input = format!("{}.{}", parts[0], parts[1]);
2446 let decoding_key = jsonwebtoken::DecodingKey::from_ec_pem(backend.public_pem.as_bytes())
2447 .expect("decoding key");
2448 let valid = jsonwebtoken::crypto::verify(
2449 parts[2],
2450 signing_input.as_bytes(),
2451 &decoding_key,
2452 jsonwebtoken::Algorithm::ES384,
2453 )
2454 .expect("verify");
2455 assert!(valid, "ES384 signature verifies under issuer key");
2456 }
2457}