aboutsummaryrefslogtreecommitdiffstats
path: root/components/net
diff options
context:
space:
mode:
Diffstat (limited to 'components/net')
-rw-r--r--components/net/fetch/methods.rs78
-rw-r--r--components/net/http_loader.rs2
-rw-r--r--components/net/lib.rs2
-rw-r--r--components/net/subresource_integrity.rs177
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)
+}