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