fraiseql_core/security/auth_middleware/signing_key.rs
1//! Signing key configuration for JWT verification.
2
3use jsonwebtoken::{Algorithm, DecodingKey};
4use zeroize::Zeroizing;
5
6use crate::security::errors::SecurityError;
7
8// ============================================================================
9// Signing Key Configuration
10// ============================================================================
11
12/// Signing key for JWT signature verification.
13///
14/// Supports both symmetric (HS256) and asymmetric (RS256/RS384/RS512) algorithms.
15#[derive(Debug, Clone)]
16#[non_exhaustive]
17pub enum SigningKey {
18 /// HMAC-SHA256 symmetric key.
19 ///
20 /// Use for internal services where the same secret is shared
21 /// between token issuer and validator.
22 Hs256(Zeroizing<Vec<u8>>),
23
24 /// HMAC-SHA384 symmetric key.
25 Hs384(Zeroizing<Vec<u8>>),
26
27 /// HMAC-SHA512 symmetric key.
28 Hs512(Zeroizing<Vec<u8>>),
29
30 /// RSA public key in PEM format (RS256 algorithm).
31 ///
32 /// Use for external identity providers. The public key is used
33 /// to verify tokens signed with the provider's private key.
34 Rs256Pem(String),
35
36 /// RSA public key in PEM format (RS384 algorithm).
37 Rs384Pem(String),
38
39 /// RSA public key in PEM format (RS512 algorithm).
40 Rs512Pem(String),
41
42 /// RSA public key components (n, e) for RS256.
43 ///
44 /// Use when receiving keys from JWKS endpoints.
45 Rs256Components {
46 /// RSA modulus (n) in base64url encoding
47 n: String,
48 /// RSA exponent (e) in base64url encoding
49 e: String,
50 },
51}
52
53impl SigningKey {
54 /// Create an HS256 signing key from a secret string.
55 #[must_use]
56 pub fn hs256(secret: &str) -> Self {
57 Self::Hs256(Zeroizing::new(secret.as_bytes().to_vec()))
58 }
59
60 /// Create an HS256 signing key from raw bytes.
61 #[must_use]
62 pub fn hs256_bytes(secret: &[u8]) -> Self {
63 Self::Hs256(Zeroizing::new(secret.to_vec()))
64 }
65
66 /// Create an RS256 signing key from PEM-encoded public key.
67 #[must_use]
68 pub fn rs256_pem(pem: &str) -> Self {
69 Self::Rs256Pem(pem.to_string())
70 }
71
72 /// Create an RS256 signing key from RSA components.
73 ///
74 /// This is useful when parsing JWKS responses.
75 #[must_use]
76 pub fn rs256_components(n: &str, e: &str) -> Self {
77 Self::Rs256Components {
78 n: n.to_string(),
79 e: e.to_string(),
80 }
81 }
82
83 /// Get the algorithm for this signing key.
84 #[must_use]
85 pub const fn algorithm(&self) -> Algorithm {
86 match self {
87 Self::Hs256(_) => Algorithm::HS256,
88 Self::Hs384(_) => Algorithm::HS384,
89 Self::Hs512(_) => Algorithm::HS512,
90 Self::Rs256Pem(_) | Self::Rs256Components { .. } => Algorithm::RS256,
91 Self::Rs384Pem(_) => Algorithm::RS384,
92 Self::Rs512Pem(_) => Algorithm::RS512,
93 }
94 }
95
96 /// Convert to a jsonwebtoken `DecodingKey`.
97 pub(super) fn to_decoding_key(&self) -> std::result::Result<DecodingKey, SecurityError> {
98 match self {
99 Self::Hs256(secret) | Self::Hs384(secret) | Self::Hs512(secret) => {
100 Ok(DecodingKey::from_secret(secret))
101 },
102 Self::Rs256Pem(pem) | Self::Rs384Pem(pem) | Self::Rs512Pem(pem) => {
103 DecodingKey::from_rsa_pem(pem.as_bytes()).map_err(|e| {
104 SecurityError::SecurityConfigError(format!("Invalid RSA PEM key: {e}"))
105 })
106 },
107 Self::Rs256Components { n, e } => DecodingKey::from_rsa_components(n, e).map_err(|e| {
108 SecurityError::SecurityConfigError(format!("Invalid RSA components: {e}"))
109 }),
110 }
111 }
112}