oauth_provider_rs/
secure_string.rs1use anyhow::{Result, anyhow};
2use ring::rand::SecureRandom;
3
4pub struct SecureStringGenerator {
6 rng: ring::rand::SystemRandom,
7}
8
9pub const TOKEN_LENGTH: usize = 32;
11pub const CLIENT_ID_LENGTH: usize = 16;
12pub const CLIENT_SECRET_LENGTH: usize = 32;
13pub const AUTH_CODE_LENGTH: usize = 32;
14pub const GRANT_ID_LENGTH: usize = 16;
15
16const CHARACTERS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
19
20impl SecureStringGenerator {
21 pub fn new() -> Self {
23 Self {
24 rng: ring::rand::SystemRandom::new(),
25 }
26 }
27
28 pub fn generate_random_string(&self, length: usize) -> Result<String> {
31 if length == 0 {
32 return Ok(String::new());
33 }
34
35 let mut result = String::with_capacity(length);
36 let mut values = vec![0u8; length];
37
38 self.rng
40 .fill(&mut values)
41 .map_err(|_| anyhow!("Failed to generate random bytes"))?;
42
43 for value in values {
45 let char_index = value as usize % CHARACTERS.len();
46 result.push(CHARACTERS[char_index] as char);
47 }
48
49 Ok(result)
50 }
51
52 pub fn generate_client_id(&self) -> Result<String> {
54 self.generate_random_string(CLIENT_ID_LENGTH)
55 }
56
57 pub fn generate_client_secret(&self) -> Result<String> {
59 self.generate_random_string(CLIENT_SECRET_LENGTH)
60 }
61
62 pub fn generate_access_token_secret(&self) -> Result<String> {
64 self.generate_random_string(TOKEN_LENGTH)
65 }
66
67 pub fn generate_refresh_token_secret(&self) -> Result<String> {
69 self.generate_random_string(TOKEN_LENGTH)
70 }
71
72 pub fn generate_auth_code_secret(&self) -> Result<String> {
74 self.generate_random_string(AUTH_CODE_LENGTH)
75 }
76
77 pub fn generate_grant_id(&self) -> Result<String> {
79 self.generate_random_string(GRANT_ID_LENGTH)
80 }
81
82 pub fn generate_pkce_code_verifier(&self) -> Result<String> {
85 self.generate_random_string(43)
86 }
87
88 pub fn generate_state_parameter(&self) -> Result<String> {
90 self.generate_random_string(32)
91 }
92
93 pub fn generate_nonce(&self) -> Result<String> {
95 self.generate_random_string(32)
96 }
97}
98
99impl Default for SecureStringGenerator {
100 fn default() -> Self {
101 Self::new()
102 }
103}
104
105static GLOBAL_GENERATOR: std::sync::LazyLock<SecureStringGenerator> =
107 std::sync::LazyLock::new(|| SecureStringGenerator::new());
108
109pub fn generate_random_string(length: usize) -> Result<String> {
111 GLOBAL_GENERATOR.generate_random_string(length)
112}
113
114pub fn generate_client_id() -> Result<String> {
116 GLOBAL_GENERATOR.generate_client_id()
117}
118
119pub fn generate_client_secret() -> Result<String> {
121 GLOBAL_GENERATOR.generate_client_secret()
122}
123
124pub fn generate_access_token_secret() -> Result<String> {
126 GLOBAL_GENERATOR.generate_access_token_secret()
127}
128
129pub fn generate_refresh_token_secret() -> Result<String> {
131 GLOBAL_GENERATOR.generate_refresh_token_secret()
132}
133
134pub fn generate_auth_code_secret() -> Result<String> {
136 GLOBAL_GENERATOR.generate_auth_code_secret()
137}
138
139pub fn generate_grant_id() -> Result<String> {
141 GLOBAL_GENERATOR.generate_grant_id()
142}
143
144pub fn generate_pkce_code_verifier() -> Result<String> {
146 GLOBAL_GENERATOR.generate_pkce_code_verifier()
147}
148
149pub fn generate_state_parameter() -> Result<String> {
151 GLOBAL_GENERATOR.generate_state_parameter()
152}
153
154pub fn generate_nonce() -> Result<String> {
156 GLOBAL_GENERATOR.generate_nonce()
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use std::collections::HashSet;
163
164 #[test]
165 fn test_generate_random_string() {
166 let generator = SecureStringGenerator::new();
167
168 for length in [0, 1, 16, 32, 64, 128] {
170 let result = generator.generate_random_string(length).unwrap();
171 assert_eq!(result.len(), length);
172
173 for ch in result.chars() {
175 assert!(CHARACTERS.contains(&(ch as u8)));
176 }
177 }
178 }
179
180 #[test]
181 fn test_string_uniqueness() {
182 let generator = SecureStringGenerator::new();
183 let mut strings = HashSet::new();
184
185 for _ in 0..1000 {
187 let s = generator.generate_random_string(32).unwrap();
188 assert!(strings.insert(s), "Generated duplicate string");
189 }
190 }
191
192 #[test]
193 fn test_character_set_validation() {
194 let generator = SecureStringGenerator::new();
195 let result = generator.generate_random_string(10000).unwrap();
196
197 let mut found_uppercase = false;
198 let mut found_lowercase = false;
199 let mut found_digit = false;
200
201 for ch in result.chars() {
202 if ch.is_ascii_uppercase() {
203 found_uppercase = true;
204 } else if ch.is_ascii_lowercase() {
205 found_lowercase = true;
206 } else if ch.is_ascii_digit() {
207 found_digit = true;
208 }
209
210 assert!(ch.is_ascii_alphanumeric());
212 }
213
214 assert!(found_uppercase);
216 assert!(found_lowercase);
217 assert!(found_digit);
218 }
219
220 #[test]
221 fn test_specific_length_generators() {
222 let generator = SecureStringGenerator::new();
223
224 let client_id = generator.generate_client_id().unwrap();
226 assert_eq!(client_id.len(), CLIENT_ID_LENGTH);
227
228 let client_secret = generator.generate_client_secret().unwrap();
230 assert_eq!(client_secret.len(), CLIENT_SECRET_LENGTH);
231
232 let access_token = generator.generate_access_token_secret().unwrap();
234 assert_eq!(access_token.len(), TOKEN_LENGTH);
235
236 let refresh_token = generator.generate_refresh_token_secret().unwrap();
238 assert_eq!(refresh_token.len(), TOKEN_LENGTH);
239
240 let auth_code = generator.generate_auth_code_secret().unwrap();
242 assert_eq!(auth_code.len(), AUTH_CODE_LENGTH);
243
244 let grant_id = generator.generate_grant_id().unwrap();
246 assert_eq!(grant_id.len(), GRANT_ID_LENGTH);
247 }
248
249 #[test]
250 fn test_pkce_code_verifier_generation() {
251 let generator = SecureStringGenerator::new();
252 let verifier = generator.generate_pkce_code_verifier().unwrap();
253
254 assert_eq!(verifier.len(), 43);
256
257 for ch in verifier.chars() {
259 assert!(ch.is_ascii_alphanumeric());
260 }
261 }
262
263 #[test]
264 fn test_global_generator_functions() {
265 let client_id = generate_client_id().unwrap();
267 assert_eq!(client_id.len(), CLIENT_ID_LENGTH);
268
269 let client_secret = generate_client_secret().unwrap();
270 assert_eq!(client_secret.len(), CLIENT_SECRET_LENGTH);
271
272 let access_token = generate_access_token_secret().unwrap();
273 assert_eq!(access_token.len(), TOKEN_LENGTH);
274
275 let refresh_token = generate_refresh_token_secret().unwrap();
276 assert_eq!(refresh_token.len(), TOKEN_LENGTH);
277
278 let auth_code = generate_auth_code_secret().unwrap();
279 assert_eq!(auth_code.len(), AUTH_CODE_LENGTH);
280
281 let grant_id = generate_grant_id().unwrap();
282 assert_eq!(grant_id.len(), GRANT_ID_LENGTH);
283
284 let state = generate_state_parameter().unwrap();
285 assert_eq!(state.len(), 32);
286
287 let nonce = generate_nonce().unwrap();
288 assert_eq!(nonce.len(), 32);
289 }
290
291 #[test]
292 fn test_empty_string_generation() {
293 let generator = SecureStringGenerator::new();
294 let result = generator.generate_random_string(0).unwrap();
295 assert!(result.is_empty());
296 }
297
298 #[test]
299 fn test_distribution_fairness() {
300 let generator = SecureStringGenerator::new();
301 let mut char_counts = [0; 62]; let large_string = generator.generate_random_string(62000).unwrap();
305
306 for ch in large_string.chars() {
307 let char_byte = ch as u8;
308 if let Some(index) = CHARACTERS.iter().position(|&x| x == char_byte) {
309 char_counts[index] += 1;
310 }
311 }
312
313 for count in char_counts.iter() {
317 assert!(*count > 500, "Character distribution too uneven: {}", count);
318 assert!(
319 *count < 1500,
320 "Character distribution too uneven: {}",
321 count
322 );
323 }
324 }
325
326 #[test]
327 fn test_multiple_generators() {
328 let gen1 = SecureStringGenerator::new();
330 let gen2 = SecureStringGenerator::new();
331
332 let s1 = gen1.generate_random_string(32).unwrap();
333 let s2 = gen2.generate_random_string(32).unwrap();
334
335 assert_ne!(s1, s2);
337 }
338
339 #[test]
340 fn test_constants_match_typescript() {
341 assert_eq!(TOKEN_LENGTH, 32);
343 assert_eq!(CLIENT_ID_LENGTH, 16);
344 assert_eq!(CLIENT_SECRET_LENGTH, 32);
345 assert_eq!(AUTH_CODE_LENGTH, 32);
346 assert_eq!(GRANT_ID_LENGTH, 16);
347 }
348
349 #[test]
350 fn test_character_set_matches_typescript() {
351 let expected = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
353 let actual = std::str::from_utf8(CHARACTERS).unwrap();
354 assert_eq!(actual, expected);
355 }
356}