mirror of
https://github.com/servo/servo.git
synced 2025-07-22 23:03:42 +01:00
Implement Subresource Integrity
Implemented response validation part of https://w3c.github.io/webappsec-subresource-integrity/. Implemented step eighteen of the main fetch. If a request has integrity metadata, then following steps are performed *Wait for response body *If the response does not have a termination reason and response does not match request’s integrity metadata, set response to a network error.# Please enter the commit message for your changes. Lines starting
This commit is contained in:
parent
496447a363
commit
a3026499f4
19 changed files with 439 additions and 260 deletions
18
Cargo.lock
generated
18
Cargo.lock
generated
|
@ -1108,7 +1108,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"html5ever-atoms 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"html5ever-atoms 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"mac 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"mac 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"phf 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
"phf 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -1121,7 +1121,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "html5ever-atoms"
|
name = "html5ever-atoms"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -1306,7 +1306,7 @@ dependencies = [
|
||||||
"gfx_traits 0.0.1",
|
"gfx_traits 0.0.1",
|
||||||
"heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"html5ever-atoms 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"html5ever-atoms 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -2264,7 +2264,7 @@ dependencies = [
|
||||||
"heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"html5ever 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"html5ever 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"html5ever-atoms 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"html5ever-atoms 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
"hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"hyper_serde 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"hyper_serde 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"image 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"image 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -2324,7 +2324,7 @@ dependencies = [
|
||||||
"gfx_traits 0.0.1",
|
"gfx_traits 0.0.1",
|
||||||
"heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"html5ever-atoms 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"html5ever-atoms 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -2740,7 +2740,7 @@ dependencies = [
|
||||||
"fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"html5ever-atoms 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"html5ever-atoms 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libbindgen 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libbindgen 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -2779,7 +2779,7 @@ dependencies = [
|
||||||
"app_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"app_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"cssparser 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"cssparser 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"euclid 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"euclid 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"html5ever-atoms 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"html5ever-atoms 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -3312,7 +3312,7 @@ name = "xml5ever"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"html5ever-atoms 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"html5ever-atoms 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"mac 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"mac 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"phf 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
"phf 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -3414,7 +3414,7 @@ dependencies = [
|
||||||
"checksum heartbeats-simple-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53c4b67617665d7f4172f381f9843c1bec6a4fccc9a9226529e5b1be40dc1301"
|
"checksum heartbeats-simple-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53c4b67617665d7f4172f381f9843c1bec6a4fccc9a9226529e5b1be40dc1301"
|
||||||
"checksum hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2da7d3a34cf6406d9d700111b8eafafe9a251de41ae71d8052748259343b58"
|
"checksum hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2da7d3a34cf6406d9d700111b8eafafe9a251de41ae71d8052748259343b58"
|
||||||
"checksum html5ever 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a358fef34c3334e92cd34d83ce870a386334e605e7abe987a69a7078a4142c69"
|
"checksum html5ever 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a358fef34c3334e92cd34d83ce870a386334e605e7abe987a69a7078a4142c69"
|
||||||
"checksum html5ever-atoms 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fd3fc831590ee7fcf693c673e4e3cbe14fbda44dc0f26d9bdc79cfc9f551dc05"
|
"checksum html5ever-atoms 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4109e35fec157307b918eb9d5b7018e2fa771aea0c04831e22003ac4722fbd1b"
|
||||||
"checksum httparse 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a6e7a63e511f9edffbab707141fbb8707d1a3098615fb2adbd5769cdfcc9b17d"
|
"checksum httparse 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a6e7a63e511f9edffbab707141fbb8707d1a3098615fb2adbd5769cdfcc9b17d"
|
||||||
"checksum hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)" = "bcb3fc65554155980167fb821d05c7c66177f92464976c0b676a19d9e03387a7"
|
"checksum hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)" = "bcb3fc65554155980167fb821d05c7c66177f92464976c0b676a19d9e03387a7"
|
||||||
"checksum hyper_serde 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "572d2168173019de312a050a24f2ad33ac2ac7895a2139fbf21ee6b6f470a24e"
|
"checksum hyper_serde 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "572d2168173019de312a050a24f2ad33ac2ac7895a2139fbf21ee6b6f470a24e"
|
||||||
|
|
|
@ -24,6 +24,7 @@ use std::io::Read;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::mpsc::{Sender, Receiver};
|
use std::sync::mpsc::{Sender, Receiver};
|
||||||
|
use subresource_integrity::is_response_integrity_valid;
|
||||||
|
|
||||||
pub type Target<'a> = &'a mut (FetchTaskTarget + Send);
|
pub type Target<'a> = &'a mut (FetchTaskTarget + Send);
|
||||||
|
|
||||||
|
@ -268,6 +269,7 @@ pub fn main_fetch(request: Rc<Request>,
|
||||||
response
|
response
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut response_loaded = false;
|
||||||
{
|
{
|
||||||
// Step 14
|
// Step 14
|
||||||
let network_error_res;
|
let network_error_res;
|
||||||
|
@ -297,50 +299,33 @@ pub fn main_fetch(request: Rc<Request>,
|
||||||
let mut body = internal_response.body.lock().unwrap();
|
let mut body = internal_response.body.lock().unwrap();
|
||||||
*body = ResponseBody::Empty;
|
*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
|
// Step 19
|
||||||
if request.synchronous {
|
if request.synchronous {
|
||||||
// process_response is not supposed to be used
|
// process_response is not supposed to be used
|
||||||
// by sync fetch, but we overload it here for simplicity
|
// by sync fetch, but we overload it here for simplicity
|
||||||
target.process_response(&response);
|
target.process_response(&response);
|
||||||
|
if !response_loaded {
|
||||||
if let Some(ref ch) = *done_chan {
|
wait_for_response(&response, target, 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// overloaded similarly to process_response
|
// overloaded similarly to process_response
|
||||||
target.process_response_eof(&response);
|
target.process_response_eof(&response);
|
||||||
return response;
|
return response;
|
||||||
|
@ -360,13 +345,25 @@ pub fn main_fetch(request: Rc<Request>,
|
||||||
target.process_response(&response);
|
target.process_response(&response);
|
||||||
|
|
||||||
// Step 22
|
// 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 {
|
if let Some(ref ch) = *done_chan {
|
||||||
loop {
|
loop {
|
||||||
match ch.1.recv()
|
match ch.1.recv()
|
||||||
.expect("fetch worker should always send Done before terminating") {
|
.expect("fetch worker should always send Done before terminating") {
|
||||||
Data::Payload(vec) => {
|
Data::Payload(vec) => {
|
||||||
target.process_response_chunk(vec);
|
target.process_response_chunk(vec);
|
||||||
}
|
},
|
||||||
Data::Done => break,
|
Data::Done => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -381,12 +378,6 @@ pub fn main_fetch(request: Rc<Request>,
|
||||||
assert!(*body == ResponseBody::Empty)
|
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)
|
/// [Basic fetch](https://fetch.spec.whatwg.org#basic-fetch)
|
||||||
|
@ -518,3 +509,4 @@ fn is_null_body_status(status: &Option<StatusCode>) -> bool {
|
||||||
_ => false
|
_ => false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1378,7 +1378,7 @@ fn cors_check(request: Rc<Request>, response: &Response) -> Result<(), ()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 6
|
// Step 6
|
||||||
let credentials = request.headers.borrow().get::<AccessControlAllowCredentials>().cloned();
|
let credentials = response.headers.get::<AccessControlAllowCredentials>().cloned();
|
||||||
|
|
||||||
// Step 7
|
// Step 7
|
||||||
if credentials.is_some() {
|
if credentials.is_some() {
|
||||||
|
|
|
@ -60,8 +60,8 @@ pub mod image_cache_thread;
|
||||||
pub mod mime_classifier;
|
pub mod mime_classifier;
|
||||||
pub mod resource_thread;
|
pub mod resource_thread;
|
||||||
mod storage_thread;
|
mod storage_thread;
|
||||||
|
pub mod subresource_integrity;
|
||||||
mod websocket_loader;
|
mod websocket_loader;
|
||||||
|
|
||||||
/// An implementation of the [Fetch specification](https://fetch.spec.whatwg.org/)
|
/// An implementation of the [Fetch specification](https://fetch.spec.whatwg.org/)
|
||||||
pub mod fetch {
|
pub mod fetch {
|
||||||
pub mod cors_cache;
|
pub mod cors_cache;
|
||||||
|
|
177
components/net/subresource_integrity.rs
Normal file
177
components/net/subresource_integrity.rs
Normal file
|
@ -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)
|
||||||
|
}
|
|
@ -158,6 +158,7 @@ pub struct RequestInit {
|
||||||
pub referrer_policy: Option<ReferrerPolicy>,
|
pub referrer_policy: Option<ReferrerPolicy>,
|
||||||
pub pipeline_id: Option<PipelineId>,
|
pub pipeline_id: Option<PipelineId>,
|
||||||
pub redirect_mode: RedirectMode,
|
pub redirect_mode: RedirectMode,
|
||||||
|
pub integrity_metadata: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for RequestInit {
|
impl Default for RequestInit {
|
||||||
|
@ -181,6 +182,7 @@ impl Default for RequestInit {
|
||||||
referrer_policy: None,
|
referrer_policy: None,
|
||||||
pipeline_id: None,
|
pipeline_id: None,
|
||||||
redirect_mode: RedirectMode::Follow,
|
redirect_mode: RedirectMode::Follow,
|
||||||
|
integrity_metadata: "".to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -291,6 +293,7 @@ impl Request {
|
||||||
req.referrer_policy.set(init.referrer_policy);
|
req.referrer_policy.set(init.referrer_policy);
|
||||||
req.pipeline_id.set(init.pipeline_id);
|
req.pipeline_id.set(init.pipeline_id);
|
||||||
req.redirect_mode.set(init.redirect_mode);
|
req.redirect_mode.set(init.redirect_mode);
|
||||||
|
*req.integrity_metadata.borrow_mut() = init.integrity_metadata;
|
||||||
req
|
req
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -243,16 +243,24 @@ impl HTMLLinkElement {
|
||||||
Some(ref value) => &***value,
|
Some(ref value) => &***value,
|
||||||
None => "",
|
None => "",
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut css_parser = CssParser::new(&mq_str);
|
let mut css_parser = CssParser::new(&mq_str);
|
||||||
let media = parse_media_query_list(&mut css_parser);
|
let media = parse_media_query_list(&mut css_parser);
|
||||||
|
|
||||||
|
let im_attribute = element.get_attribute(&ns!(), &local_name!("integrity"));
|
||||||
|
let integrity_val = im_attribute.r().map(|a| a.value());
|
||||||
|
let integrity_metadata = match integrity_val {
|
||||||
|
Some(ref value) => &***value,
|
||||||
|
None => "",
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: #8085 - Don't load external stylesheets if the node's mq
|
// TODO: #8085 - Don't load external stylesheets if the node's mq
|
||||||
// doesn't match.
|
// doesn't match.
|
||||||
let loader = StylesheetLoader::for_element(self.upcast());
|
let loader = StylesheetLoader::for_element(self.upcast());
|
||||||
loader.load(StylesheetContextSource::LinkElement {
|
loader.load(StylesheetContextSource::LinkElement {
|
||||||
url: url,
|
url: url,
|
||||||
media: Some(media),
|
media: Some(media),
|
||||||
});
|
}, integrity_metadata.to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_favicon_url(&self, rel: &str, href: &str, sizes: &Option<String>) {
|
fn handle_favicon_url(&self, rel: &str, href: &str, sizes: &Option<String>) {
|
||||||
|
@ -328,6 +336,12 @@ impl HTMLLinkElementMethods for HTMLLinkElement {
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-link-media
|
// https://html.spec.whatwg.org/multipage/#dom-link-media
|
||||||
make_setter!(SetMedia, "media");
|
make_setter!(SetMedia, "media");
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/#dom-link-integrity
|
||||||
|
make_getter!(Integrity, "integrity");
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/#dom-link-integrity
|
||||||
|
make_setter!(SetIntegrity, "integrity");
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-link-hreflang
|
// https://html.spec.whatwg.org/multipage/#dom-link-hreflang
|
||||||
make_getter!(Hreflang, "hreflang");
|
make_getter!(Hreflang, "hreflang");
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,6 @@ use std::ascii::AsciiExt;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use style::str::{HTML_SPACE_CHARACTERS, StaticStringVec};
|
use style::str::{HTML_SPACE_CHARACTERS, StaticStringVec};
|
||||||
|
|
||||||
#[dom_struct]
|
#[dom_struct]
|
||||||
pub struct HTMLScriptElement {
|
pub struct HTMLScriptElement {
|
||||||
htmlelement: HTMLElement,
|
htmlelement: HTMLElement,
|
||||||
|
@ -221,6 +220,7 @@ impl PreInvoke for ScriptContext {}
|
||||||
fn fetch_a_classic_script(script: &HTMLScriptElement,
|
fn fetch_a_classic_script(script: &HTMLScriptElement,
|
||||||
url: ServoUrl,
|
url: ServoUrl,
|
||||||
cors_setting: Option<CorsSettings>,
|
cors_setting: Option<CorsSettings>,
|
||||||
|
integrity_metadata: String,
|
||||||
character_encoding: EncodingRef) {
|
character_encoding: EncodingRef) {
|
||||||
let doc = document_from_node(script);
|
let doc = document_from_node(script);
|
||||||
|
|
||||||
|
@ -245,6 +245,7 @@ fn fetch_a_classic_script(script: &HTMLScriptElement,
|
||||||
pipeline_id: Some(script.global().pipeline_id()),
|
pipeline_id: Some(script.global().pipeline_id()),
|
||||||
referrer_url: Some(doc.url()),
|
referrer_url: Some(doc.url()),
|
||||||
referrer_policy: doc.get_referrer_policy(),
|
referrer_policy: doc.get_referrer_policy(),
|
||||||
|
integrity_metadata: integrity_metadata,
|
||||||
.. RequestInit::default()
|
.. RequestInit::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -365,7 +366,13 @@ impl HTMLScriptElement {
|
||||||
|
|
||||||
// TODO: Step 15: Nonce.
|
// TODO: Step 15: Nonce.
|
||||||
|
|
||||||
// TODO: Step 16: Parser state.
|
// Step 16: Integrity Metadata
|
||||||
|
let im_attribute = element.get_attribute(&ns!(), &local_name!("integrity"));
|
||||||
|
let integrity_val = im_attribute.r().map(|a| a.value());
|
||||||
|
let integrity_metadata = match integrity_val {
|
||||||
|
Some(ref value) => &***value,
|
||||||
|
None => "",
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: Step 17: environment settings object.
|
// TODO: Step 17: environment settings object.
|
||||||
|
|
||||||
|
@ -393,7 +400,7 @@ impl HTMLScriptElement {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Step 18.6.
|
// Step 18.6.
|
||||||
fetch_a_classic_script(self, url, cors_setting, encoding);
|
fetch_a_classic_script(self, url, cors_setting, integrity_metadata.to_owned(), encoding);
|
||||||
|
|
||||||
true
|
true
|
||||||
},
|
},
|
||||||
|
@ -675,6 +682,11 @@ impl HTMLScriptElementMethods for HTMLScriptElement {
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-script-defer
|
// https://html.spec.whatwg.org/multipage/#dom-script-defer
|
||||||
make_bool_setter!(SetDefer, "defer");
|
make_bool_setter!(SetDefer, "defer");
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/#dom-script-integrity
|
||||||
|
make_getter!(Integrity, "integrity");
|
||||||
|
// https://html.spec.whatwg.org/multipage/#dom-script-integrity
|
||||||
|
make_setter!(SetIntegrity, "integrity");
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-script-event
|
// https://html.spec.whatwg.org/multipage/#dom-script-event
|
||||||
make_getter!(Event, "event");
|
make_getter!(Event, "event");
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-script-event
|
// https://html.spec.whatwg.org/multipage/#dom-script-event
|
||||||
|
|
|
@ -11,6 +11,7 @@ interface HTMLLinkElement : HTMLElement {
|
||||||
attribute DOMString media;
|
attribute DOMString media;
|
||||||
attribute DOMString hreflang;
|
attribute DOMString hreflang;
|
||||||
attribute DOMString type;
|
attribute DOMString type;
|
||||||
|
attribute DOMString integrity;
|
||||||
// [SameObject, PutForwards=value] readonly attribute DOMTokenList sizes;
|
// [SameObject, PutForwards=value] readonly attribute DOMTokenList sizes;
|
||||||
|
|
||||||
// also has obsolete members
|
// also has obsolete members
|
||||||
|
|
|
@ -12,6 +12,7 @@ interface HTMLScriptElement : HTMLElement {
|
||||||
attribute DOMString? crossOrigin;
|
attribute DOMString? crossOrigin;
|
||||||
[Pure]
|
[Pure]
|
||||||
attribute DOMString text;
|
attribute DOMString text;
|
||||||
|
attribute DOMString integrity;
|
||||||
|
|
||||||
// also has obsolete members
|
// also has obsolete members
|
||||||
};
|
};
|
||||||
|
|
|
@ -193,7 +193,7 @@ impl<'a> StylesheetLoader<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> StylesheetLoader<'a> {
|
impl<'a> StylesheetLoader<'a> {
|
||||||
pub fn load(&self, source: StylesheetContextSource) {
|
pub fn load(&self, source: StylesheetContextSource, integrity_metadata: String) {
|
||||||
let url = source.url();
|
let url = source.url();
|
||||||
let document = document_from_node(self.elem);
|
let document = document_from_node(self.elem);
|
||||||
let context = Arc::new(Mutex::new(StylesheetContext {
|
let context = Arc::new(Mutex::new(StylesheetContext {
|
||||||
|
@ -234,6 +234,7 @@ impl<'a> StylesheetLoader<'a> {
|
||||||
pipeline_id: Some(self.elem.global().pipeline_id()),
|
pipeline_id: Some(self.elem.global().pipeline_id()),
|
||||||
referrer_url: Some(document.url()),
|
referrer_url: Some(document.url()),
|
||||||
referrer_policy: referrer_policy,
|
referrer_policy: referrer_policy,
|
||||||
|
integrity_metadata: integrity_metadata,
|
||||||
.. RequestInit::default()
|
.. RequestInit::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -243,6 +244,6 @@ impl<'a> StylesheetLoader<'a> {
|
||||||
|
|
||||||
impl<'a> StyleStylesheetLoader for StylesheetLoader<'a> {
|
impl<'a> StyleStylesheetLoader for StylesheetLoader<'a> {
|
||||||
fn request_stylesheet(&self, import: &Arc<RwLock<ImportRule>>) {
|
fn request_stylesheet(&self, import: &Arc<RwLock<ImportRule>>) {
|
||||||
self.load(StylesheetContextSource::Import(import.clone()))
|
self.load(StylesheetContextSource::Import(import.clone()), "".to_owned())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -233,7 +233,6 @@ fn test_cors_preflight_fetch() {
|
||||||
let _ = server.close();
|
let _ = server.close();
|
||||||
|
|
||||||
assert!(!fetch_response.is_network_error());
|
assert!(!fetch_response.is_network_error());
|
||||||
|
|
||||||
match *fetch_response.body.lock().unwrap() {
|
match *fetch_response.body.lock().unwrap() {
|
||||||
ResponseBody::Done(ref body) => assert_eq!(&**body, ACK),
|
ResponseBody::Done(ref body) => assert_eq!(&**body, ACK),
|
||||||
_ => panic!()
|
_ => panic!()
|
||||||
|
@ -556,6 +555,53 @@ fn test_fetch_with_hsts() {
|
||||||
"https");
|
"https");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fetch_with_sri_network_error() {
|
||||||
|
static MESSAGE: &'static [u8] = b"alert('Hello, Network Error');";
|
||||||
|
let handler = move |_: HyperRequest, response: HyperResponse| {
|
||||||
|
response.send(MESSAGE).unwrap();
|
||||||
|
};
|
||||||
|
let (mut server, url) = make_server(handler);
|
||||||
|
|
||||||
|
let origin = Origin::Origin(url.origin());
|
||||||
|
let mut request = Request::new(url, Some(origin), false, None);
|
||||||
|
*request.referrer.borrow_mut() = Referrer::NoReferrer;
|
||||||
|
// To calulate hash use :
|
||||||
|
// echo -n "alert('Hello, Network Error');" | openssl dgst -sha384 -binary | openssl base64 -A
|
||||||
|
*request.integrity_metadata.borrow_mut() =
|
||||||
|
"sha384-H8BRh8j48O9oYatfu5AZzq6A9RINhZO5H16dQZngK7T62em8MUt1FLm52t+eX6xO".to_owned();
|
||||||
|
// Set the flag.
|
||||||
|
request.local_urls_only = false;
|
||||||
|
|
||||||
|
let response = fetch(request, None);
|
||||||
|
|
||||||
|
let _ = server.close();
|
||||||
|
assert!(response.is_network_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fetch_with_sri_sucess() {
|
||||||
|
static MESSAGE: &'static [u8] = b"alert('Hello, world.');";
|
||||||
|
let handler = move |_: HyperRequest, response: HyperResponse| {
|
||||||
|
response.send(MESSAGE).unwrap();
|
||||||
|
};
|
||||||
|
let (mut server, url) = make_server(handler);
|
||||||
|
|
||||||
|
let origin = Origin::Origin(url.origin());
|
||||||
|
let mut request = Request::new(url, Some(origin), false, None);
|
||||||
|
*request.referrer.borrow_mut() = Referrer::NoReferrer;
|
||||||
|
// To calulate hash use :
|
||||||
|
// echo -n "alert('Hello, Network Error');" | openssl dgst -sha384 -binary | openssl base64 -A
|
||||||
|
*request.integrity_metadata.borrow_mut() =
|
||||||
|
"sha384-H8BRh8j48O9oYatfu5AZzq6A9RINhZO5H16dQZngK7T62em8MUt1FLm52t+eX6xO".to_owned();
|
||||||
|
// Set the flag.
|
||||||
|
request.local_urls_only = false;
|
||||||
|
|
||||||
|
let response = fetch(request, None);
|
||||||
|
|
||||||
|
let _ = server.close();
|
||||||
|
assert_eq!(response_is_done(&response), true);
|
||||||
|
}
|
||||||
fn setup_server_and_fetch(message: &'static [u8], redirect_cap: u32) -> Response {
|
fn setup_server_and_fetch(message: &'static [u8], redirect_cap: u32) -> Response {
|
||||||
let handler = move |request: HyperRequest, mut response: HyperResponse| {
|
let handler = move |request: HyperRequest, mut response: HyperResponse| {
|
||||||
let redirects = match request.uri {
|
let redirects = match request.uri {
|
||||||
|
@ -742,7 +788,6 @@ fn test_fetch_async_returns_complete_response() {
|
||||||
let fetch_response = fetch(request, None);
|
let fetch_response = fetch(request, None);
|
||||||
|
|
||||||
let _ = server.close();
|
let _ = server.close();
|
||||||
|
|
||||||
assert_eq!(response_is_done(&fetch_response), true);
|
assert_eq!(response_is_done(&fetch_response), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ extern crate url;
|
||||||
#[cfg(test)] mod hsts;
|
#[cfg(test)] mod hsts;
|
||||||
#[cfg(test)] mod http_loader;
|
#[cfg(test)] mod http_loader;
|
||||||
#[cfg(test)] mod filemanager_thread;
|
#[cfg(test)] mod filemanager_thread;
|
||||||
|
#[cfg(test)] mod subresource_integrity;
|
||||||
|
|
||||||
use devtools_traits::DevtoolsControlMsg;
|
use devtools_traits::DevtoolsControlMsg;
|
||||||
use hyper::server::{Handler, Listening, Server};
|
use hyper::server::{Handler, Listening, Server};
|
||||||
|
|
92
tests/unit/net/subresource_integrity.rs
Normal file
92
tests/unit/net/subresource_integrity.rs
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
/* 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::subresource_integrity::{SriEntry, get_prioritized_hash_function, get_strongest_metadata};
|
||||||
|
use net::subresource_integrity::{is_response_integrity_valid, parsed_metadata};
|
||||||
|
use net_traits::response::{Response, ResponseBody};
|
||||||
|
use servo_url::ServoUrl;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_prioritized_hash_function() {
|
||||||
|
let mut algorithm = get_prioritized_hash_function("sha256", "sha256");
|
||||||
|
assert_eq!(algorithm, None);
|
||||||
|
|
||||||
|
algorithm = get_prioritized_hash_function("sha256", "sha384");
|
||||||
|
assert_eq!(algorithm.unwrap(), "sha384");
|
||||||
|
|
||||||
|
algorithm = get_prioritized_hash_function("sha384", "sha512");
|
||||||
|
assert_eq!(algorithm.unwrap(), "sha512");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parsed_metadata_without_options() {
|
||||||
|
let integrity_metadata = "sha384-Hash1";
|
||||||
|
let ref parsed_metadata: SriEntry = parsed_metadata(integrity_metadata)[0];
|
||||||
|
|
||||||
|
assert_eq!(parsed_metadata.alg, "sha384");
|
||||||
|
assert_eq!(parsed_metadata.val, "Hash1");
|
||||||
|
assert!(parsed_metadata.opt.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parsed_metadata_with_options() {
|
||||||
|
let integrity_metadata = "sha384-Hash1?opt=23";
|
||||||
|
let ref parsed_metadata: SriEntry = parsed_metadata(integrity_metadata)[0];
|
||||||
|
|
||||||
|
assert_eq!(parsed_metadata.alg, "sha384");
|
||||||
|
assert_eq!(parsed_metadata.val, "Hash1");
|
||||||
|
assert!(parsed_metadata.opt.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parsed_metadata_with_malformed_integrity() {
|
||||||
|
let integrity_metadata = "Not a valid integrity";
|
||||||
|
let ref parsed_metadata_list: Vec<SriEntry> = parsed_metadata(integrity_metadata);
|
||||||
|
|
||||||
|
assert!(parsed_metadata_list.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_strongest_metadata_two_same_algorithm() {
|
||||||
|
let integrity_metadata = "sha512-Hash1 sha512-Hash2?opt=23";
|
||||||
|
let parsed_metadata_list: Vec<SriEntry> = parsed_metadata(integrity_metadata);
|
||||||
|
|
||||||
|
let strong_metadata: Vec<SriEntry> = get_strongest_metadata(parsed_metadata_list);
|
||||||
|
assert_eq!(strong_metadata.len(), 2);
|
||||||
|
assert_eq!(strong_metadata[0].alg, strong_metadata[1].alg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_strongest_metadata_different_algorithm() {
|
||||||
|
let integrity_metadata = "sha256-Hash0 sha384-Hash1 sha512-Hash2?opt=23";
|
||||||
|
let parsed_metadata_list: Vec<SriEntry> = parsed_metadata(integrity_metadata);
|
||||||
|
|
||||||
|
let strong_metadata: Vec<SriEntry> = get_strongest_metadata(parsed_metadata_list);
|
||||||
|
assert_eq!(strong_metadata.len(), 1);
|
||||||
|
assert_eq!(strong_metadata[0].alg, "sha512");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_response_integrity_valid() {
|
||||||
|
let url: ServoUrl = ServoUrl::parse("http://servo.org").unwrap();
|
||||||
|
let response: Response = Response::new(url);
|
||||||
|
|
||||||
|
let integrity_metadata = "sha384-H8BRh8j48O9oYatfu5AZzq6A9RINhZO5H16dQZngK7T62em8MUt1FLm52t+eX6xO";
|
||||||
|
let response_body = "alert('Hello, world.');".to_owned().into_bytes();
|
||||||
|
|
||||||
|
*response.body.lock().unwrap() = ResponseBody::Done(response_body);
|
||||||
|
assert!(is_response_integrity_valid(integrity_metadata, &response));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_response_integrity_invalid() {
|
||||||
|
let url: ServoUrl = ServoUrl::parse("http://servo.org").unwrap();
|
||||||
|
let response: Response = Response::new(url);
|
||||||
|
|
||||||
|
let integrity_metadata = "sha256-H8BRh8j48O9oYatfu5AZzq6A9RINhZO5H16dQZngK7T62em8MUt1FLm52t+eX6xO";
|
||||||
|
let response_body = "alert('Hello, world.');".to_owned().into_bytes();
|
||||||
|
|
||||||
|
*response.body.lock().unwrap() = ResponseBody::Done(response_body);
|
||||||
|
assert!(!is_response_integrity_valid(integrity_metadata, &response));
|
||||||
|
}
|
|
@ -43,6 +43,8 @@ skip: true
|
||||||
skip: false
|
skip: false
|
||||||
[referrer-policy]
|
[referrer-policy]
|
||||||
skip: false
|
skip: false
|
||||||
|
[subresource-integrity]
|
||||||
|
skip: false
|
||||||
[touch-events]
|
[touch-events]
|
||||||
skip: false
|
skip: false
|
||||||
[typedarrays]
|
[typedarrays]
|
||||||
|
|
|
@ -9870,18 +9870,12 @@
|
||||||
[HTMLLinkElement interface: attribute nonce]
|
[HTMLLinkElement interface: attribute nonce]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[HTMLLinkElement interface: attribute integrity]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[HTMLLinkElement interface: attribute referrerPolicy]
|
[HTMLLinkElement interface: attribute referrerPolicy]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[HTMLLinkElement interface: document.createElement("link") must inherit property "nonce" with the proper type (5)]
|
[HTMLLinkElement interface: document.createElement("link") must inherit property "nonce" with the proper type (5)]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[HTMLLinkElement interface: document.createElement("link") must inherit property "integrity" with the proper type (6)]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[HTMLLinkElement interface: document.createElement("link") must inherit property "sizes" with the proper type (9)]
|
[HTMLLinkElement interface: document.createElement("link") must inherit property "sizes" with the proper type (9)]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -10116,102 +10116,6 @@
|
||||||
[link.nonce: IDL set to object "test-valueOf"]
|
[link.nonce: IDL set to object "test-valueOf"]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[link.integrity: typeof IDL attribute]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: IDL get with DOM attribute unset]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: setAttribute() to ""]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: setAttribute() to " \\0\\x01\\x02\\x03\\x04\\x05\\x06\\x07 \\b\\t\\n\\v\\f\\r\\x0e\\x0f \\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17 \\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f foo "]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: setAttribute() to undefined]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: setAttribute() to 7]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: setAttribute() to 1.5]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: setAttribute() to true]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: setAttribute() to false]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: setAttribute() to object "[object Object\]"]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: setAttribute() to NaN]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: setAttribute() to Infinity]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: setAttribute() to -Infinity]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: setAttribute() to "\\0"]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: setAttribute() to null]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: setAttribute() to object "test-toString"]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: setAttribute() to object "test-valueOf"]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: IDL set to ""]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: IDL set to " \\0\\x01\\x02\\x03\\x04\\x05\\x06\\x07 \\b\\t\\n\\v\\f\\r\\x0e\\x0f \\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17 \\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f foo "]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: IDL set to undefined]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: IDL set to 7]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: IDL set to 1.5]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: IDL set to true]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: IDL set to false]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: IDL set to object "[object Object\]"]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: IDL set to NaN]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: IDL set to Infinity]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: IDL set to -Infinity]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: IDL set to "\\0"]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: IDL set to null]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: IDL set to object "test-toString"]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.integrity: IDL set to object "test-valueOf"]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[link.referrerPolicy: typeof IDL attribute]
|
[link.referrerPolicy: typeof IDL attribute]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -17610,99 +17610,3 @@
|
||||||
[script.nonce: IDL set to object "test-valueOf"]
|
[script.nonce: IDL set to object "test-valueOf"]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[script.integrity: typeof IDL attribute]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: IDL get with DOM attribute unset]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: setAttribute() to ""]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: setAttribute() to " \\0\\x01\\x02\\x03\\x04\\x05\\x06\\x07 \\b\\t\\n\\v\\f\\r\\x0e\\x0f \\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17 \\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f foo "]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: setAttribute() to undefined]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: setAttribute() to 7]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: setAttribute() to 1.5]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: setAttribute() to true]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: setAttribute() to false]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: setAttribute() to object "[object Object\]"]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: setAttribute() to NaN]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: setAttribute() to Infinity]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: setAttribute() to -Infinity]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: setAttribute() to "\\0"]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: setAttribute() to null]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: setAttribute() to object "test-toString"]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: setAttribute() to object "test-valueOf"]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: IDL set to ""]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: IDL set to " \\0\\x01\\x02\\x03\\x04\\x05\\x06\\x07 \\b\\t\\n\\v\\f\\r\\x0e\\x0f \\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17 \\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f foo "]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: IDL set to undefined]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: IDL set to 7]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: IDL set to 1.5]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: IDL set to true]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: IDL set to false]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: IDL set to object "[object Object\]"]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: IDL set to NaN]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: IDL set to Infinity]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: IDL set to -Infinity]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: IDL set to "\\0"]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: IDL set to null]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: IDL set to object "test-toString"]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[script.integrity: IDL set to object "test-valueOf"]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
[subresource-integrity.sub.html]
|
||||||
|
type: testharness
|
||||||
|
expected: TIMEOUT
|
||||||
|
[Style: <crossorigin='anonymous'> with correct hash, ACAO: *]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Style: Same-origin with correct sha256 and sha512 hash, rel='alternate stylesheet' enabled]
|
||||||
|
expected: NOTRUN
|
||||||
|
|
||||||
|
[Style: Same-origin with incorrect sha256 and sha512 hash, rel='alternate stylesheet' enabled]
|
||||||
|
expected: NOTRUN
|
||||||
|
|
||||||
|
[Style: Same-origin with incorrect hash.]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Style: Same-origin with sha256 match, sha512 mismatch]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Style: <crossorigin='use-credentials'> with correct hash, CORS-eligible]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Style: <crossorigin='anonymous'> with CORS-ineligible resource]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Style: Cross-origin, not CORS request, with correct hash]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Style: Cross-origin, not CORS request, with hash mismatch]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Style: <crossorigin='use-credentials'> with incorrect hash CORS-eligible]
|
||||||
|
expected: FAIL
|
||||||
|
|
||||||
|
[Style: <crossorigin='anonymous'> with incorrect hash, ACAO: *]
|
||||||
|
expected: FAIL
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue