net/
subresource_integrity.rs1use std::iter::Filter;
6use std::str::Split;
7use std::sync::MutexGuard;
8
9use base64::Engine;
10use generic_array::ArrayLength;
11use net_traits::response::{Response, ResponseBody, ResponseType};
12use sha2::{Digest, Sha256, Sha384, Sha512};
13
14const SUPPORTED_ALGORITHM: &[&str] = &["sha256", "sha384", "sha512"];
15pub type StaticCharVec = &'static [char];
16pub static HTML_SPACE_CHARACTERS: StaticCharVec =
20 &['\u{0020}', '\u{0009}', '\u{000a}', '\u{000c}', '\u{000d}'];
21#[derive(Clone)]
22pub struct SriEntry {
23 pub alg: String,
24 pub val: String,
25 pub opt: Option<String>,
29}
30
31impl SriEntry {
32 pub fn new(alg: &str, val: &str, opt: Option<String>) -> SriEntry {
33 SriEntry {
34 alg: alg.to_owned(),
35 val: val.to_owned(),
36 opt,
37 }
38 }
39}
40
41pub fn parsed_metadata(integrity_metadata: &str) -> Vec<SriEntry> {
43 let mut result = vec![];
45
46 let tokens = split_html_space_chars(integrity_metadata);
48 for token in tokens {
49 let parsed_data: Vec<&str> = token.split('-').collect();
50
51 if parsed_data.len() > 1 {
52 let alg = parsed_data[0];
53
54 if !SUPPORTED_ALGORITHM.contains(&alg) {
55 continue;
56 }
57
58 let data: Vec<&str> = parsed_data[1].split('?').collect();
59 let digest = data[0];
60
61 let opt = if data.len() > 1 {
62 Some(data[1].to_owned())
63 } else {
64 None
65 };
66
67 result.push(SriEntry::new(alg, digest, opt));
68 }
69 }
70
71 result
72}
73
74pub fn get_prioritized_hash_function(
76 hash_func_left: &str,
77 hash_func_right: &str,
78) -> Option<String> {
79 let left_priority = SUPPORTED_ALGORITHM
80 .iter()
81 .position(|s| *s == hash_func_left)
82 .unwrap();
83 let right_priority = SUPPORTED_ALGORITHM
84 .iter()
85 .position(|s| *s == hash_func_right)
86 .unwrap();
87
88 if left_priority == right_priority {
89 return None;
90 }
91 if left_priority > right_priority {
92 Some(hash_func_left.to_owned())
93 } else {
94 Some(hash_func_right.to_owned())
95 }
96}
97
98pub fn get_strongest_metadata(integrity_metadata_list: Vec<SriEntry>) -> Vec<SriEntry> {
100 let mut result: Vec<SriEntry> = vec![integrity_metadata_list[0].clone()];
101 let mut current_algorithm = result[0].alg.clone();
102
103 for integrity_metadata in &integrity_metadata_list[1..] {
104 let prioritized_hash =
105 get_prioritized_hash_function(&integrity_metadata.alg, ¤t_algorithm);
106 if prioritized_hash.is_none() {
107 result.push(integrity_metadata.clone());
108 } else if let Some(algorithm) = prioritized_hash {
109 if algorithm != current_algorithm {
110 result = vec![integrity_metadata.clone()];
111 current_algorithm = algorithm;
112 }
113 }
114 }
115
116 result
117}
118
119fn apply_algorithm_to_response<S: ArrayLength<u8>, D: Digest<OutputSize = S>>(
121 body: MutexGuard<ResponseBody>,
122 mut hasher: D,
123) -> String {
124 if let ResponseBody::Done(ref vec) = *body {
125 hasher.update(vec);
126 let response_digest = hasher.finalize(); base64::engine::general_purpose::STANDARD.encode(&response_digest)
128 } else {
129 unreachable!("Tried to calculate digest of incomplete response body")
130 }
131}
132
133fn is_eligible_for_integrity_validation(response: &Response) -> bool {
135 matches!(
136 response.response_type,
137 ResponseType::Basic | ResponseType::Default | ResponseType::Cors
138 )
139}
140
141pub fn is_response_integrity_valid(integrity_metadata: &str, response: &Response) -> bool {
143 let parsed_metadata_list: Vec<SriEntry> = parsed_metadata(integrity_metadata);
144
145 if parsed_metadata_list.is_empty() {
147 return true;
148 }
149
150 if !is_eligible_for_integrity_validation(response) {
152 return false;
153 }
154
155 let metadata: Vec<SriEntry> = get_strongest_metadata(parsed_metadata_list);
157 for item in metadata {
158 let body = response.body.lock().unwrap();
159 let algorithm = item.alg;
160 let digest = item.val;
161
162 let hashed = match &*algorithm {
163 "sha256" => apply_algorithm_to_response(body, Sha256::new()),
164 "sha384" => apply_algorithm_to_response(body, Sha384::new()),
165 "sha512" => apply_algorithm_to_response(body, Sha512::new()),
166 _ => continue,
167 };
168
169 if hashed == digest {
170 return true;
171 }
172 }
173
174 false
175}
176
177pub fn split_html_space_chars(s: &str) -> Filter<Split<'_, StaticCharVec>, fn(&&str) -> bool> {
178 fn not_empty(&split: &&str) -> bool {
179 !split.is_empty()
180 }
181 s.split(HTML_SPACE_CHARACTERS)
182 .filter(not_empty as fn(&&str) -> bool)
183}