From d64aa9c5bf5cb50451d9e2c36ca0a1965f4fbcb1 Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Mon, 27 Mar 2017 14:14:34 +0200 Subject: [PATCH 1/4] Simplify should_be_blocked_due_to_nosniff --- components/net/fetch/methods.rs | 38 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/components/net/fetch/methods.rs b/components/net/fetch/methods.rs index 841ba94dd1c..27af691c7ed 100644 --- a/components/net/fetch/methods.rs +++ b/components/net/fetch/methods.rs @@ -11,7 +11,8 @@ use http_loader::{HttpState, determine_request_referrer, http_fetch, set_default use hyper::Error; use hyper::error::Result as HyperResult; use hyper::header::{Accept, AcceptLanguage, ContentLanguage, ContentType}; -use hyper::header::{Header, HeaderFormat, HeaderView, QualityItem, Referer as RefererHeader, q, qitem}; +use hyper::header::{Header, HeaderFormat, HeaderView, Headers, QualityItem}; +use hyper::header::{Referer as RefererHeader, q, qitem}; use hyper::method::Method; use hyper::mime::{Mime, SubLevel, TopLevel}; use hyper::status::StatusCode; @@ -282,13 +283,14 @@ pub fn main_fetch(request: Rc, // Step 16 // TODO Blocking for CSP, mixed content, MIME type let blocked_error_response; - let internal_response = if !response.is_network_error() && should_block_nosniff(&request, &response) { - // Defer rebinding result - blocked_error_response = Response::network_error(NetworkError::Internal("Blocked by nosniff".into())); - &blocked_error_response - } else { - internal_response - }; + let internal_response = + if !response.is_network_error() && should_be_blocked_due_to_nosniff(request.type_, &response.headers) { + // Defer rebinding result + blocked_error_response = Response::network_error(NetworkError::Internal("Blocked by nosniff".into())); + &blocked_error_response + } else { + internal_response + }; // Step 17 // We check `internal_response` since we did not mutate `response` in the previous step. @@ -525,7 +527,7 @@ fn is_null_body_status(status: &Option) -> bool { } /// https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-nosniff? -fn should_block_nosniff(request: &Request, response: &Response) -> bool { +fn should_be_blocked_due_to_nosniff(request_type: Type, response_headers: &Headers) -> bool { /// https://fetch.spec.whatwg.org/#x-content-type-options-header /// This is needed to parse `X-Content-Type-Options` according to spec, /// which requires that we inspect only the first value. @@ -533,7 +535,7 @@ fn should_block_nosniff(request: &Request, response: &Response) -> bool { /// A [unit-like struct](https://doc.rust-lang.org/book/structs.html#unit-like-structs) /// is sufficient since a valid header implies that we use `nosniff`. #[derive(Debug, Clone, Copy)] - struct XContentTypeOptions(); + struct XContentTypeOptions; impl Header for XContentTypeOptions { fn header_name() -> &'static str { @@ -545,7 +547,7 @@ fn should_block_nosniff(request: &Request, response: &Response) -> bool { raw.first() .and_then(|v| str::from_utf8(v).ok()) .and_then(|s| match s.trim().to_lowercase().as_str() { - "nosniff" => Some(XContentTypeOptions()), + "nosniff" => Some(XContentTypeOptions), _ => None }) .ok_or(Error::Header) @@ -558,16 +560,14 @@ fn should_block_nosniff(request: &Request, response: &Response) -> bool { } } - match response.headers.get::() { - None => return false, // Step 1 - _ => () // Step 2 & 3 are implemented by the XContentTypeOptions struct - }; + // Steps 1-3. + if response_headers.get::().is_none() { + return false; + } // Step 4 // Note: an invalid MIME type will produce a `None`. - let content_type_header = response.headers.get::(); - // Step 5 - let type_ = request.type_; + let content_type_header = response_headers.get::(); /// https://html.spec.whatwg.org/multipage/#scriptingLanguages #[inline] @@ -596,7 +596,7 @@ fn should_block_nosniff(request: &Request, response: &Response) -> bool { let text_css: Mime = mime!(Text / Css); // Assumes str::starts_with is equivalent to mime::TopLevel - return match type_ { + return match request_type { // Step 6 Type::Script => { match content_type_header { From e2e2d42e38107f5d623175eb905e90f9d580369e Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Mon, 27 Mar 2017 14:51:10 +0200 Subject: [PATCH 2/4] Introduce http_loader::is_redirect_status --- components/net/http_loader.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index ca972034471..f4ee1da4251 100644 --- a/components/net/http_loader.rs +++ b/components/net/http_loader.rs @@ -629,11 +629,7 @@ pub fn http_fetch(request: Rc, // Step 5 match response.actual_response().status { // Code 301, 302, 303, 307, 308 - Some(StatusCode::MovedPermanently) | - Some(StatusCode::Found) | - Some(StatusCode::SeeOther) | - Some(StatusCode::TemporaryRedirect) | - Some(StatusCode::PermanentRedirect) => { + status if status.map_or(false, is_redirect_status) => { response = match request.redirect_mode.get() { RedirectMode::Error => Response::network_error(NetworkError::Internal("Redirect mode error".into())), RedirectMode::Manual => { @@ -1418,3 +1414,15 @@ fn response_needs_revalidation(_response: &Response) -> bool { // TODO this function false } + +/// https://fetch.spec.whatwg.org/#redirect-status +fn is_redirect_status(status: StatusCode) -> bool { + match status { + StatusCode::MovedPermanently | + StatusCode::Found | + StatusCode::SeeOther | + StatusCode::TemporaryRedirect | + StatusCode::PermanentRedirect => true, + _ => false, + } +} From 7a4632bfa22c8bb83fbfe0c0241411362ee4dffd Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Tue, 28 Mar 2017 00:37:25 +0200 Subject: [PATCH 3/4] Introduce create_ssl_context --- components/net/connector.rs | 8 ++++++-- components/net/http_loader.rs | 5 +++-- components/net/resource_thread.rs | 10 ++++++---- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/components/net/connector.rs b/components/net/connector.rs index 8211e415f34..0bd2d225e34 100644 --- a/components/net/connector.rs +++ b/components/net/connector.rs @@ -27,15 +27,19 @@ const DEFAULT_CIPHERS: &'static str = concat!( "AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA" ); -pub fn create_http_connector(certificate_file: &str) -> Arc> { +pub fn create_ssl_context(certificate_file: &str) -> Arc { let mut context = SslContext::new(SslMethod::Sslv23).unwrap(); context.set_CA_file(&resources_dir_path() .expect("Need certificate file to make network requests") .join(certificate_file)).unwrap(); context.set_cipher_list(DEFAULT_CIPHERS).unwrap(); context.set_options(SSL_OP_NO_SSLV2 | SSL_OP_NO_SSLV3 | SSL_OP_NO_COMPRESSION); + Arc::new(context) +} + +pub fn create_http_connector(ssl_context: Arc) -> Arc> { let connector = HttpsConnector::new(ServoSslClient { - context: Arc::new(context) + context: ssl_context, }); Arc::new(Pool::with_connector(Default::default(), connector)) diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index f4ee1da4251..ec8bfed9666 100644 --- a/components/net/http_loader.rs +++ b/components/net/http_loader.rs @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use brotli::Decompressor; -use connector::{Connector, create_http_connector}; +use connector::{Connector, create_http_connector, create_ssl_context}; use cookie; use cookie_storage::CookieStorage; use devtools_traits::{ChromeToDevtoolsControlMsg, DevtoolsControlMsg, HttpRequest as DevtoolsHttpRequest}; @@ -77,11 +77,12 @@ pub struct HttpState { impl HttpState { pub fn new(certificate_path: &str) -> HttpState { + let ssl_context = create_ssl_context(certificate_path); HttpState { hsts_list: Arc::new(RwLock::new(HstsList::new())), cookie_jar: Arc::new(RwLock::new(CookieStorage::new(150))), auth_cache: Arc::new(RwLock::new(AuthCache::new())), - connector_pool: create_http_connector(certificate_path), + connector_pool: create_http_connector(ssl_context), } } } diff --git a/components/net/resource_thread.rs b/components/net/resource_thread.rs index 483505f8ffb..ec8ef44df7a 100644 --- a/components/net/resource_thread.rs +++ b/components/net/resource_thread.rs @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //! A thread that takes a URL and streams back the binary data. -use connector::{Connector, create_http_connector}; +use connector::{Connector, create_http_connector, create_ssl_context}; use cookie; use cookie_rs; use cookie_storage::CookieStorage; @@ -104,17 +104,18 @@ fn create_resource_groups(config_dir: Option<&Path>) read_json_from_file(&mut hsts_list, config_dir, "hsts_list.json"); read_json_from_file(&mut cookie_jar, config_dir, "cookie_jar.json"); } + let ssl_context = create_ssl_context("certs"); let resource_group = ResourceGroup { cookie_jar: Arc::new(RwLock::new(cookie_jar)), auth_cache: Arc::new(RwLock::new(auth_cache)), hsts_list: Arc::new(RwLock::new(hsts_list.clone())), - connector: create_http_connector("certs"), + connector: create_http_connector(ssl_context.clone()), }; let private_resource_group = ResourceGroup { cookie_jar: Arc::new(RwLock::new(CookieStorage::new(150))), auth_cache: Arc::new(RwLock::new(AuthCache::new())), hsts_list: Arc::new(RwLock::new(HstsList::new())), - connector: create_http_connector("certs"), + connector: create_http_connector(ssl_context), }; (resource_group, private_resource_group) } @@ -319,12 +320,13 @@ impl CoreResourceManager { init: RequestInit, mut sender: IpcSender, group: &ResourceGroup) { + let ssl_context = create_ssl_context("certs"); let http_state = HttpState { hsts_list: group.hsts_list.clone(), cookie_jar: group.cookie_jar.clone(), auth_cache: group.auth_cache.clone(), // FIXME(#15694): use group.connector.clone() instead. - connector_pool: create_http_connector("certs"), + connector_pool: create_http_connector(ssl_context), }; let ua = self.user_agent.clone(); let dc = self.devtools_chan.clone(); From 02b2aa159a2019836e799c73ead43f2d189c9e2a Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Tue, 28 Mar 2017 00:52:10 +0200 Subject: [PATCH 4/4] Store SSL context in ResourceGroup This allows sharing it with the HTTP state in CoreResourceManager::fetch. --- components/net/resource_thread.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/components/net/resource_thread.rs b/components/net/resource_thread.rs index ec8ef44df7a..55afbc754d8 100644 --- a/components/net/resource_thread.rs +++ b/components/net/resource_thread.rs @@ -21,6 +21,7 @@ use net_traits::{CustomResponseMediator, ResourceId}; use net_traits::{ResourceThreads, WebSocketCommunicate, WebSocketConnectData}; use net_traits::request::{Request, RequestInit}; use net_traits::storage_thread::StorageThreadMsg; +use openssl::ssl::SslContext; use profile_traits::time::ProfilerChan; use serde::{Deserialize, Serialize}; use serde_json; @@ -46,6 +47,7 @@ pub struct ResourceGroup { cookie_jar: Arc>, auth_cache: Arc>, hsts_list: Arc>, + ssl_context: Arc, connector: Arc>, } @@ -109,12 +111,14 @@ fn create_resource_groups(config_dir: Option<&Path>) cookie_jar: Arc::new(RwLock::new(cookie_jar)), auth_cache: Arc::new(RwLock::new(auth_cache)), hsts_list: Arc::new(RwLock::new(hsts_list.clone())), + ssl_context: ssl_context.clone(), connector: create_http_connector(ssl_context.clone()), }; let private_resource_group = ResourceGroup { cookie_jar: Arc::new(RwLock::new(CookieStorage::new(150))), auth_cache: Arc::new(RwLock::new(AuthCache::new())), hsts_list: Arc::new(RwLock::new(HstsList::new())), + ssl_context: ssl_context.clone(), connector: create_http_connector(ssl_context), }; (resource_group, private_resource_group) @@ -320,13 +324,12 @@ impl CoreResourceManager { init: RequestInit, mut sender: IpcSender, group: &ResourceGroup) { - let ssl_context = create_ssl_context("certs"); let http_state = HttpState { hsts_list: group.hsts_list.clone(), cookie_jar: group.cookie_jar.clone(), auth_cache: group.auth_cache.clone(), // FIXME(#15694): use group.connector.clone() instead. - connector_pool: create_http_connector(ssl_context), + connector_pool: create_http_connector(group.ssl_context.clone()), }; let ua = self.user_agent.clone(); let dc = self.devtools_chan.clone();