https_everywhere_lib_core/
updater.rs

1use 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    /// Returns an updater with the rulesets, update channels, storage, and interval to check for
53    /// new rulesets
54    ///
55    /// # Arguments
56    ///
57    /// * `rulesets` - A ruleset struct to update
58    /// * `update_channels` - The update channels where to look for new rulesets
59    /// * `storage` - The storage engine for key-value pairs
60    /// * `default_rulesets` - An optional string representing the default rulesets, which may or
61    /// may not be replaced by updates
62    /// * `periodicity` - The interval to check for new rulesets
63    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    /// Get the current timestamp in seconds
74    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    /// Returns an `Option<i32>` optional timestamp if there are new rulesets.  If no new rulesets
81    /// are available, or if there is a failure for any reason, return `None`
82    ///
83    /// # Arguments
84    ///
85    /// * `uc` - The update channel to check for new rulesets on
86    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    /// Given an update channel and timestamp, this returns a result-wrapped tuple, the first value the first value is
118    /// a `Vec<u8>` of the signature file, the second is a `Vec<u8>` of the rulesets file.
119    ///
120    /// # Arguments
121    ///
122    /// * `rulesets_timestamp` - The timestamp for the rulesets
123    /// * `update_channel` - The update channel to download rulesets for
124    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        // TODO: Use futures to asynchronously fetch signature and rulesets
128
129        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    /// If the given signature for the given rulesets verifies with the key stored in the given
148    /// update channel, store this update channel in the struct storage layer.  Returns a
149    /// result-wrapped unit
150    ///
151    /// # Arguments
152    ///
153    /// * `signature` - A SHA256 RSA PSS signature
154    /// * `rulesets` - Rulesets to check the signature for
155    /// * `rulesets_timestamp` - The timestamp for the rulesets, which we use to verify that it
156    /// matches the timestamp in the signed rulesets JSON
157    /// * `update_channel` - Contains the key which we verify the signatures with
158    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    /// Perform a check for updates.  For all ruleset update channels:
193    ///
194    /// 1. Check if new rulesets exist by requesting a defined endpoint for a timestamp, which is
195    ///    compared to a stored timestamp
196    /// 2. If new rulesets exist, download them along with a signature
197    /// 3. Verify if the signature is valid, and if so...
198    /// 4. Store the rulesets
199    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    /// Modify rulesets struct to apply the stored rulesets
241    pub fn apply_stored_rulesets(&mut self) {
242        type OkResult = (Value, Option<String>, bool);
243
244        // TODO: Use futures to asynchronously apply stored rulesets
245        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    /// Return the time until we should check for new rulesets, in seconds
283    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    /// Clear the stored rulesets for any update channels which replace the default rulesets.  This
291    /// should be run when a new version of the extension is released, so the bundled rulesets are
292    /// not overwritten by old stored rulesets.
293    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}