Skip to main content

uselesskey_rsa/
spec.rs

1/// Specification for RSA key generation.
2///
3/// This struct defines the parameters for generating RSA keypairs.
4///
5/// # Examples
6///
7/// ```
8/// use uselesskey_rsa::RsaSpec;
9///
10/// // Common preset for JWT RS256 signing
11/// let spec = RsaSpec::rs256();
12/// assert_eq!(spec.bits, 2048);
13/// assert_eq!(spec.exponent, 65537);
14///
15/// // Custom bit size (exponent defaults to 65537)
16/// let spec = RsaSpec::new(4096);
17/// assert_eq!(spec.bits, 4096);
18/// ```
19#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
20pub struct RsaSpec {
21    /// RSA modulus size in bits. Must be at least 1024.
22    pub bits: usize,
23    /// Public exponent. Currently only 65537 is supported.
24    pub exponent: u32,
25}
26
27impl RsaSpec {
28    /// Spec suitable for RS256 JWT signing in most ecosystems.
29    ///
30    /// Returns a spec with 2048 bits and exponent 65537.
31    ///
32    /// # Examples
33    ///
34    /// ```
35    /// use uselesskey_rsa::RsaSpec;
36    ///
37    /// let spec = RsaSpec::rs256();
38    /// assert_eq!(spec.bits, 2048);
39    /// assert_eq!(spec.exponent, 65537);
40    /// ```
41    pub fn rs256() -> Self {
42        Self {
43            bits: 2048,
44            exponent: 65537,
45        }
46    }
47
48    /// Create a spec with custom bit size and default exponent (65537).
49    ///
50    /// # Panics
51    ///
52    /// The factory will panic if `bits < 1024`.
53    ///
54    /// # Examples
55    ///
56    /// ```
57    /// use uselesskey_rsa::RsaSpec;
58    ///
59    /// let spec = RsaSpec::new(4096);
60    /// assert_eq!(spec.bits, 4096);
61    /// assert_eq!(spec.exponent, 65537);
62    /// ```
63    pub fn new(bits: usize) -> Self {
64        Self {
65            bits,
66            exponent: 65537,
67        }
68    }
69
70    /// Stable encoding for cache keys / deterministic derivation.
71    ///
72    /// If you change this, bump the derivation version in `uselesskey-core`.
73    ///
74    /// # Examples
75    ///
76    /// ```
77    /// use uselesskey_rsa::RsaSpec;
78    ///
79    /// let spec = RsaSpec::rs256();
80    /// let bytes = spec.stable_bytes();
81    /// assert_eq!(bytes.len(), 8);
82    /// ```
83    pub fn stable_bytes(&self) -> [u8; 8] {
84        let bits = u32::try_from(self.bits).unwrap_or(u32::MAX);
85        let mut out = [0u8; 8];
86        out[..4].copy_from_slice(&bits.to_be_bytes());
87        out[4..].copy_from_slice(&self.exponent.to_be_bytes());
88        out
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn rs256_defaults_are_expected() {
98        let spec = RsaSpec::rs256();
99        assert_eq!(spec.bits, 2048);
100        assert_eq!(spec.exponent, 65537);
101    }
102
103    #[test]
104    fn new_sets_bits_and_default_exponent() {
105        let spec = RsaSpec::new(4096);
106        assert_eq!(spec.bits, 4096);
107        assert_eq!(spec.exponent, 65537);
108    }
109
110    #[test]
111    fn stable_bytes_encodes_bits_and_exponent() {
112        let spec = RsaSpec::rs256();
113        let bytes = spec.stable_bytes();
114        assert_eq!(&bytes[..4], &2048u32.to_be_bytes());
115        assert_eq!(&bytes[4..], &65537u32.to_be_bytes());
116    }
117
118    #[test]
119    fn stable_bytes_clamps_large_bits() {
120        let spec = RsaSpec::new(usize::MAX);
121        let bytes = spec.stable_bytes();
122        assert_eq!(&bytes[..4], &u32::MAX.to_be_bytes());
123    }
124}