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}