Skip to main content

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}