1use base64::{engine::general_purpose::STANDARD, Engine as _};
2use ring::rand::{SecureRandom, SystemRandom};
3
4pub fn generate_csp_nonce() -> String {
6 let rng = SystemRandom::new();
7 let mut bytes = [0u8; 32];
8 rng.fill(&mut bytes).expect("RNG failure");
9 STANDARD.encode(bytes)
10}
11
12pub fn build_csp_header(nonce: &str) -> String {
16 format!(
17 "default-src 'none'; \
18 script-src 'nonce-{nonce}' 'self'; \
19 style-src 'nonce-{nonce}' 'self'; \
20 connect-src 'self'; \
21 img-src 'self' data:; \
22 font-src 'self'; \
23 frame-ancestors 'none'; \
24 base-uri 'none'; \
25 form-action 'self'"
26 )
27}
28
29#[cfg(test)]
30mod tests {
31 use super::*;
32
33 #[test]
34 fn nonce_is_unique() {
35 let a = generate_csp_nonce();
36 let b = generate_csp_nonce();
37 assert_ne!(a, b);
38 }
39
40 #[test]
41 fn nonce_is_base64() {
42 let nonce = generate_csp_nonce();
43 assert_eq!(nonce.len(), 44); assert!(STANDARD.decode(&nonce).is_ok());
45 }
46
47 #[test]
48 fn csp_contains_nonce() {
49 let nonce = generate_csp_nonce();
50 let csp = build_csp_header(&nonce);
51 assert!(csp.contains(&format!("'nonce-{nonce}'")));
52 }
53
54 #[test]
55 fn csp_is_strict() {
56 let csp = build_csp_header("test");
57 assert!(csp.contains("default-src 'none'"));
58 assert!(!csp.contains("unsafe-inline"));
59 assert!(!csp.contains("unsafe-eval"));
60 }
61}