diff options
Diffstat (limited to 'components/net')
-rw-r--r-- | components/net/fetch/methods.rs | 78 | ||||
-rw-r--r-- | components/net/http_loader.rs | 2 | ||||
-rw-r--r-- | components/net/lib.rs | 2 | ||||
-rw-r--r-- | components/net/subresource_integrity.rs | 177 |
4 files changed, 214 insertions, 45 deletions
diff --git a/components/net/fetch/methods.rs b/components/net/fetch/methods.rs index 0a284ee4016..44a603025b0 100644 --- a/components/net/fetch/methods.rs +++ b/components/net/fetch/methods.rs @@ -24,6 +24,7 @@ use std::io::Read; use std::mem; use std::rc::Rc; use std::sync::mpsc::{Sender, Receiver}; +use subresource_integrity::is_response_integrity_valid; pub type Target<'a> = &'a mut (FetchTaskTarget + Send); @@ -268,6 +269,7 @@ pub fn main_fetch(request: Rc<Request>, response }; + let mut response_loaded = false; { // Step 14 let network_error_res; @@ -297,50 +299,33 @@ pub fn main_fetch(request: Rc<Request>, let mut body = internal_response.body.lock().unwrap(); *body = ResponseBody::Empty; } - - // Step 18 - // TODO be able to compare response integrity against request integrity metadata - // if !response.is_network_error() { - - // // Substep 1 - // response.wait_until_done(); - - // // Substep 2 - // if response.termination_reason.is_none() { - // response = Response::network_error(); - // internal_response = Response::network_error(); - // } - // } } + // Step 18 + let response = if !response.is_network_error() && *request.integrity_metadata.borrow() != "" { + // Substep 1 + wait_for_response(&response, target, done_chan); + response_loaded = true; + + // Substep 2 + let ref integrity_metadata = *request.integrity_metadata.borrow(); + if response.termination_reason.is_none() && + !is_response_integrity_valid(integrity_metadata, &response) { + Response::network_error(NetworkError::Internal("Subresource integrity validation failed".into())) + } else { + response + } + } else { + response + }; // Step 19 if request.synchronous { // process_response is not supposed to be used // by sync fetch, but we overload it here for simplicity target.process_response(&response); - - if let Some(ref ch) = *done_chan { - loop { - match ch.1.recv() - .expect("fetch worker should always send Done before terminating") { - Data::Payload(vec) => { - target.process_response_chunk(vec); - } - Data::Done => break, - } - } - } else { - let body = response.body.lock().unwrap(); - if let ResponseBody::Done(ref vec) = *body { - // in case there was no channel to wait for, the body was - // obtained synchronously via basic_fetch for data/file/about/etc - // We should still send the body across as a chunk - target.process_response_chunk(vec.clone()); - } else { - assert!(*body == ResponseBody::Empty) - } + if !response_loaded { + wait_for_response(&response, target, done_chan); } - // overloaded similarly to process_response target.process_response_eof(&response); return response; @@ -360,13 +345,25 @@ pub fn main_fetch(request: Rc<Request>, target.process_response(&response); // Step 22 + if !response_loaded { + wait_for_response(&response, target, done_chan); + } + + // Step 24 + target.process_response_eof(&response); + + // TODO remove this line when only asynchronous fetches are used + return response; +} + +fn wait_for_response(response: &Response, target: Target, done_chan: &mut DoneChannel) { if let Some(ref ch) = *done_chan { loop { match ch.1.recv() .expect("fetch worker should always send Done before terminating") { Data::Payload(vec) => { target.process_response_chunk(vec); - } + }, Data::Done => break, } } @@ -381,12 +378,6 @@ pub fn main_fetch(request: Rc<Request>, assert!(*body == ResponseBody::Empty) } } - - // Step 24 - target.process_response_eof(&response); - - // TODO remove this line when only asynchronous fetches are used - return response; } /// [Basic fetch](https://fetch.spec.whatwg.org#basic-fetch) @@ -518,3 +509,4 @@ fn is_null_body_status(status: &Option<StatusCode>) -> bool { _ => false } } + diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index de066b8248e..63fa745707b 100644 --- a/components/net/http_loader.rs +++ b/components/net/http_loader.rs @@ -1378,7 +1378,7 @@ fn cors_check(request: Rc<Request>, response: &Response) -> Result<(), ()> { } // Step 6 - let credentials = request.headers.borrow().get::<AccessControlAllowCredentials>().cloned(); + let credentials = response.headers.get::<AccessControlAllowCredentials>().cloned(); // Step 7 if credentials.is_some() { diff --git a/components/net/lib.rs b/components/net/lib.rs index 098c0ba540c..57ff2af1a0e 100644 --- a/components/net/lib.rs +++ b/components/net/lib.rs @@ -60,8 +60,8 @@ pub mod image_cache_thread; pub mod mime_classifier; pub mod resource_thread; mod storage_thread; +pub mod subresource_integrity; mod websocket_loader; - /// An implementation of the [Fetch specification](https://fetch.spec.whatwg.org/) pub mod fetch { pub mod cors_cache; diff --git a/components/net/subresource_integrity.rs b/components/net/subresource_integrity.rs new file mode 100644 index 00000000000..7268edbf80e --- /dev/null +++ b/components/net/subresource_integrity.rs @@ -0,0 +1,177 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use net_traits::response::{Response, ResponseBody, ResponseType}; +use openssl::crypto::hash::{hash, Type as MessageDigest}; +use rustc_serialize::base64::{STANDARD, ToBase64}; +use std::iter::Filter; +use std::str::Split; +use std::sync::MutexGuard; +const SUPPORTED_ALGORITHM: &'static [&'static str] = &[ + "sha256", + "sha384", + "sha512", +]; +pub type StaticCharVec = &'static [char]; +/// A "space character" according to: +/// +/// https://html.spec.whatwg.org/multipage/#space-character +pub static HTML_SPACE_CHARACTERS: StaticCharVec = &[ + '\u{0020}', + '\u{0009}', + '\u{000a}', + '\u{000c}', + '\u{000d}', +]; +#[derive(Clone)] +pub struct SriEntry { + pub alg: String, + pub val: String, + // TODO : Current version of spec does not define any option. + // Can be refactored into appropriate datastructure when future + // spec has more details. + pub opt: Option<String>, +} + +impl SriEntry { + pub fn new(alg: &str, val: &str, opt: Option<String>) -> SriEntry { + SriEntry { + alg: alg.to_owned(), + val: val.to_owned(), + opt: opt, + } + } +} + +/// https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata +pub fn parsed_metadata(integrity_metadata: &str) -> Vec<SriEntry> { + // Step 1 + let mut result = vec![]; + + // Step 3 + let tokens = split_html_space_chars(integrity_metadata); + for token in tokens { + let parsed_data: Vec<&str> = token.split("-").collect(); + + if parsed_data.len() > 1 { + let alg = parsed_data[0]; + + if !SUPPORTED_ALGORITHM.contains(&alg) { + continue; + } + + let data: Vec<&str> = parsed_data[1].split("?").collect(); + let digest = data[0]; + + let opt = if data.len() > 1 { + Some(data[1].to_owned()) + } else { + None + }; + + result.push(SriEntry::new(alg, digest, opt)); + } + } + + return result; +} + +/// https://w3c.github.io/webappsec-subresource-integrity/#getprioritizedhashfunction +pub fn get_prioritized_hash_function(hash_func_left: &str, hash_func_right: &str) -> Option<String> { + let left_priority = SUPPORTED_ALGORITHM.iter().position(|s| s.to_owned() == hash_func_left).unwrap(); + let right_priority = SUPPORTED_ALGORITHM.iter().position(|s| s.to_owned() == hash_func_right).unwrap(); + + if left_priority == right_priority { + return None; + } + if left_priority > right_priority { + Some(hash_func_left.to_owned()) + } else { + Some(hash_func_right.to_owned()) + } + +} + +/// https://w3c.github.io/webappsec-subresource-integrity/#get-the-strongest-metadata +pub fn get_strongest_metadata(integrity_metadata_list: Vec<SriEntry>) -> Vec<SriEntry> { + let mut result: Vec<SriEntry> = vec![integrity_metadata_list[0].clone()]; + let mut current_algorithm = result[0].alg.clone(); + + for integrity_metadata in &integrity_metadata_list[1..] { + let prioritized_hash = get_prioritized_hash_function(&integrity_metadata.alg, + &*current_algorithm); + if prioritized_hash.is_none() { + result.push(integrity_metadata.clone()); + } else if let Some(algorithm) = prioritized_hash { + if algorithm != current_algorithm { + result = vec![integrity_metadata.clone()]; + current_algorithm = algorithm; + } + } + } + + result +} + +/// https://w3c.github.io/webappsec-subresource-integrity/#apply-algorithm-to-response +fn apply_algorithm_to_response(body: MutexGuard<ResponseBody>, + message_digest: MessageDigest) + -> String { + if let ResponseBody::Done(ref vec) = *body { + let response_digest = hash(message_digest, vec); + response_digest.to_base64(STANDARD) + } else { + unreachable!("Tried to calculate digest of incomplete response body") + } +} + +/// https://w3c.github.io/webappsec-subresource-integrity/#is-response-eligible +fn is_eligible_for_integrity_validation(response: &Response) -> bool { + match response.response_type { + ResponseType::Basic | ResponseType::Default | ResponseType::Cors => true, + _ => false, + } +} + +/// https://w3c.github.io/webappsec-subresource-integrity/#does-response-match-metadatalist +pub fn is_response_integrity_valid(integrity_metadata: &str, response: &Response) -> bool { + let parsed_metadata_list: Vec<SriEntry> = parsed_metadata(integrity_metadata); + + // Step 2 & 4 + if parsed_metadata_list.is_empty() { + return true; + } + + // Step 3 + if !is_eligible_for_integrity_validation(response) { + return false; + } + + // Step 5 + let metadata: Vec<SriEntry> = get_strongest_metadata(parsed_metadata_list); + for item in metadata { + let body = response.body.lock().unwrap(); + let algorithm = item.alg; + let digest = item.val; + + let message_digest = match &*algorithm { + "sha256" => MessageDigest::SHA256, + "sha384" => MessageDigest::SHA384, + "sha512" => MessageDigest::SHA512, + _ => continue, + }; + + if apply_algorithm_to_response(body, message_digest) == digest { + return true; + } + } + + false +} + +pub fn split_html_space_chars<'a>(s: &'a str) -> + Filter<Split<'a, StaticCharVec>, fn(&&str) -> bool> { + fn not_empty(&split: &&str) -> bool { !split.is_empty() } + s.split(HTML_SPACE_CHARACTERS).filter(not_empty as fn(&&str) -> bool) +} |