https_everywhere_lib_core/
updater.rs1use crate::{rulesets::ENABLE_MIXED_RULESETS, rulesets::RULE_ACTIVE_STATES, RuleSets, Storage, UpdateChannel, UpdateChannels};
2use flate2::read::GzDecoder;
3use http_req::request;
4use openssl::hash::MessageDigest;
5use openssl::pkey::PKey;
6use openssl::rsa::Padding;
7use openssl::sign::Verifier;
8use serde_json::Value;
9use std::cmp;
10use std::error::Error;
11use std::fmt;
12use std::io::Read;
13use std::time::{SystemTime, UNIX_EPOCH};
14
15type Timestamp = usize;
16
17#[derive(Debug, Clone)]
18struct UpdaterError {
19 error_string: String,
20}
21
22impl UpdaterError {
23 pub fn new(error_string: String) -> UpdaterError {
24 UpdaterError {
25 error_string
26 }
27 }
28}
29
30impl fmt::Display for UpdaterError {
31 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32 write!(f, "{}", self.error_string)
33 }
34}
35
36impl Error for UpdaterError {
37 fn source(&self) -> Option<&(dyn Error + 'static)> {
38 None
39 }
40}
41
42
43pub struct Updater<'a> {
44 rulesets: &'a mut RuleSets,
45 update_channels: &'a UpdateChannels,
46 storage: &'a dyn Storage,
47 default_rulesets: Option<String>,
48 periodicity: usize,
49}
50
51impl<'a> Updater<'a> {
52 pub fn new(rulesets: &'a mut RuleSets, update_channels: &'a UpdateChannels, storage: &'a dyn Storage, default_rulesets: Option<String>, periodicity: usize) -> Updater<'a> {
64 Updater {
65 rulesets,
66 update_channels,
67 storage,
68 default_rulesets,
69 periodicity,
70 }
71 }
72
73 fn current_timestamp() -> Timestamp {
75 let since_the_epoch = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
76 let current_timestamp = since_the_epoch.as_secs();
77 current_timestamp as Timestamp
78 }
79
80 fn check_for_new_rulesets(&self, uc: &UpdateChannel) -> Option<Timestamp> {
87 let mut writer = Vec::new();
88
89 let res = match request::get(uc.update_path_prefix.clone() + "/latest-rulesets-timestamp", &mut writer) {
90 Ok(result) => result,
91 Err(_) => return None
92 };
93
94 if res.status_code().is_success() {
95 let ts_string = match String::from_utf8(writer) {
96 Ok(timestamp) => timestamp,
97 Err(_) => return None
98 };
99
100 let timestamp: Timestamp = match ts_string.trim().parse() {
101 Ok(num) => num,
102 Err(_) => return None
103 };
104
105 let stored_timestamp: Timestamp = self.storage.get_int(format!("rulesets-timestamp: {}", &uc.name)).unwrap_or(0);
106
107 if stored_timestamp < timestamp {
108 Some(timestamp)
109 } else {
110 None
111 }
112 } else {
113 None
114 }
115 }
116
117 fn get_new_rulesets(&self, rulesets_timestamp: Timestamp, update_channel: &UpdateChannel) -> Result<(Vec<u8>, Vec<u8>), Box<dyn Error>> {
125 self.storage.set_int(format!("rulesets-timestamp: {}", &update_channel.name), rulesets_timestamp);
126
127 let mut signature_writer = Vec::new();
130 let signature_res = request::get(update_channel.update_path_prefix.clone() + "/rulesets-signature." + &rulesets_timestamp.to_string() + ".sha256", &mut signature_writer)?;
131
132 if !signature_res.status_code().is_success() {
133 return Err(Box::new(UpdaterError::new(format!("{}: A non-2XX response was returned from the ruleset signature URL", &update_channel.name))));
134 }
135
136
137 let mut rulesets_writer = Vec::new();
138 let rulesets_res = request::get(update_channel.update_path_prefix.clone() + "/default.rulesets." + &rulesets_timestamp.to_string() + ".gz", &mut rulesets_writer)?;
139
140 if !rulesets_res.status_code().is_success() {
141 return Err(Box::new(UpdaterError::new(format!("{}: A non-2XX response was returned from the ruleset URL", &update_channel.name))));
142 }
143
144 Ok((signature_writer, rulesets_writer))
145 }
146
147 fn verify_and_store_new_rulesets(&mut self, signature: Vec<u8>, rulesets: Vec<u8>, rulesets_timestamp: Timestamp, update_channel: &UpdateChannel) -> Result<(), Box<dyn Error>> {
159 let update_channel_key = PKey::from_rsa(update_channel.key.clone())?;
160 let mut verifier = Verifier::new(MessageDigest::sha256(), &update_channel_key)?;
161 verifier.set_rsa_padding(Padding::PKCS1_PSS)?;
162
163 verifier.update(&rulesets)?;
164
165 if verifier.verify(&signature)? {
166 info!("{}: Downloaded ruleset signature checks out. Storing rulesets.", update_channel.name);
167
168 let mut rulesets_json_string = String::new();
169 let mut decoder = GzDecoder::new(&rulesets[..]);
170 decoder.read_to_string(&mut rulesets_json_string)?;
171
172 let rulesets_json_value: Value = serde_json::from_str(&rulesets_json_string)?;
173 match rulesets_json_value.get("timestamp") {
174 Some(Value::Number(json_timestamp)) if json_timestamp.is_i64() => {
175 if json_timestamp.as_i64().unwrap() != rulesets_timestamp as i64 {
176 return Err(Box::new(UpdaterError::new(format!("{}: JSON timestamp does not match with latest timestamp file", &update_channel.name))));
177 }
178 },
179 _ => {
180 return Err(Box::new(UpdaterError::new(format!("{}: Could not parse JSON timestamp", &update_channel.name))));
181 }
182 }
183
184 self.storage.set_string(format!("rulesets: {}", update_channel.name), rulesets_json_string);
185 } else {
186 return Err(Box::new(UpdaterError::new(format!("{}: Downloaded ruleset signature is invalid. Aborting.", &update_channel.name))));
187 }
188
189 Ok(())
190 }
191
192 pub fn perform_check(&mut self) {
200 info!("Checking for new rulesets.");
201
202 self.storage.set_int(String::from("last-checked"), Self::current_timestamp());
203
204 let extension_timestamp = self.storage.get_int(String::from("extension-timestamp")).unwrap_or(0);
205
206 let mut some_updated = false;
207 for uc in self.update_channels.get_all() {
208 if let Some(new_rulesets_timestamp) = self.check_for_new_rulesets(uc) {
209 if uc.replaces_default_rulesets && extension_timestamp > new_rulesets_timestamp {
210 info!("{}: A new ruleset bundle has been released, but it is older than the extension-bundled rulesets it replaces. Skipping.", uc.name);
211 continue;
212 }
213 info!("{}: A new ruleset bundle has been released. Downloading now.", uc.name);
214
215 let (signature, rulesets) = match self.get_new_rulesets(new_rulesets_timestamp, uc) {
216 Ok(rs_tuple) => rs_tuple,
217 Err(err) => {
218 error!("{:?}", err);
219 continue;
220 }
221 };
222
223 if let Err(err) = self.verify_and_store_new_rulesets(signature, rulesets, new_rulesets_timestamp, uc) {
224 error!("{:?}", err);
225 continue;
226 }
227
228 self.storage.set_int(format!("rulesets-stored-timestamp: {}", uc.name), new_rulesets_timestamp);
229 some_updated = true;
230 } else {
231 info!("{}: No new ruleset bundle discovered.", uc.name);
232 }
233 }
234
235 if some_updated {
236 self.apply_stored_rulesets();
237 }
238 }
239
240 pub fn apply_stored_rulesets(&mut self) {
242 type OkResult = (Value, Option<String>, bool);
243
244 let rulesets_closure = |uc: &UpdateChannel| -> Result<OkResult, Box<dyn Error>> {
246 match self.storage.get_string(format!("rulesets: {}", &uc.name)) {
247 Some(rulesets_json_string) => {
248 info!("{}: Applying stored rulesets.", &uc.name);
249
250 let rulesets_json_value: Value = serde_json::from_str(&rulesets_json_string)?;
251 let inner_rulesets: Value = rulesets_json_value.get("rulesets").unwrap().clone();
252 Ok((inner_rulesets, uc.scope.clone(), uc.replaces_default_rulesets))
253 }
254 None => Err(Box::new(UpdaterError::new(format!("{} Could not retrieve stored rulesets", &uc.name))))
255 }
256 };
257
258 let mut rulesets_tuple_results = vec![];
259 for uc in self.update_channels.get_all() {
260 rulesets_tuple_results.push(rulesets_closure(uc));
261 }
262
263 let rulesets_tuples: Vec<OkResult> = rulesets_tuple_results.into_iter().filter(|rt| rt.is_ok()).map(|rt| rt.unwrap()).collect();
264 let replaces = rulesets_tuples.iter().fold(false, |acc, rt| {
265 if rt.2 {
266 true
267 } else {
268 acc
269 }
270 });
271
272 self.rulesets.clear();
273 for rt in rulesets_tuples {
274 self.rulesets.add_all_from_serde_value(rt.0, &ENABLE_MIXED_RULESETS, &RULE_ACTIVE_STATES, &rt.1);
275 }
276
277 if !replaces && !self.default_rulesets.is_none() {
278 self.rulesets.add_all_from_json_string(&self.default_rulesets.clone().unwrap(), &ENABLE_MIXED_RULESETS, &RULE_ACTIVE_STATES, &None);
279 }
280 }
281
282 pub fn time_to_next_check(&self) -> usize {
284 let last_checked = self.storage.get_int(String::from("last-checked")).unwrap_or(0);
285 let current_timestamp = Self::current_timestamp();
286 let secs_since_last_checked = current_timestamp - last_checked;
287 cmp::max(0, self.periodicity as isize - secs_since_last_checked as isize) as usize
288 }
289
290 pub fn clear_replacement_update_channels(&self) {
294 for uc in self.update_channels.get_all() {
295 if uc.replaces_default_rulesets {
296 self.storage.set_int(format!("rulesets-timestamp: {}", &uc.name), 0);
297 self.storage.set_int(format!("rulesets-stored-timestamp: {}", &uc.name), 0);
298 self.storage.set_string(format!("rulesets: {}", &uc.name), String::from(""));
299 }
300 }
301 }
302}