From 6020b4c15c62f950346d6e446fb9a8ed04d05e13 Mon Sep 17 00:00:00 2001 From: Raghav Date: Thu, 29 Dec 2016 12:55:31 +0530 Subject: [PATCH] Implement HSTS fetch step Implemented step nine of the main fetch. If current URL scheme is 'HTTP' and current URL's host is domain and if current URL's host matched with Known HSTS Host Domain Name Matching results in either a superdomain match with an asserted includeSubDomains directive or a congruent match then we change request scheme to 'https'. This change has been made in method.rs A test case to validate this has been added in fetch.rs. For asserting https scheme, a https localhost was required. For this purpose I have created a self-signed certificate and refactored fetch-context and connector.rs to programmatically trust this certificate for running this test case. --- components/net/connector.rs | 4 +- components/net/fetch/methods.rs | 10 +++- components/net/http_loader.rs | 4 +- components/net/resource_thread.rs | 4 +- resources/privatekey_for_testing.key | 28 ++++++++++ .../self_signed_certificate_for_testing.crt | 22 ++++++++ tests/unit/net/fetch.rs | 52 ++++++++++++++++++- tests/unit/net/lib.rs | 2 +- 8 files changed, 117 insertions(+), 9 deletions(-) create mode 100644 resources/privatekey_for_testing.key create mode 100644 resources/self_signed_certificate_for_testing.crt diff --git a/components/net/connector.rs b/components/net/connector.rs index 3e4a8071f28..8211e415f34 100644 --- a/components/net/connector.rs +++ b/components/net/connector.rs @@ -27,11 +27,11 @@ const DEFAULT_CIPHERS: &'static str = concat!( "AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA" ); -pub fn create_http_connector() -> Arc> { +pub fn create_http_connector(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("certs")).unwrap(); + .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); let connector = HttpsConnector::new(ServoSslClient { diff --git a/components/net/fetch/methods.rs b/components/net/fetch/methods.rs index de41bfa202a..0a284ee4016 100644 --- a/components/net/fetch/methods.rs +++ b/components/net/fetch/methods.rs @@ -189,7 +189,15 @@ pub fn main_fetch(request: Rc, } // Step 9 - // TODO this step (HSTS) + if !request.current_url().is_secure_scheme() && request.current_url().domain().is_some() { + if context.state + .hsts_list + .read() + .unwrap() + .is_host_secure(request.current_url().domain().unwrap()) { + request.url_list.borrow_mut().last_mut().unwrap().as_mut_url().unwrap().set_scheme("https").unwrap(); + } + } // Step 10 // this step is obsoleted by fetch_async diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index 63e72db3ffa..be73b564350 100644 --- a/components/net/http_loader.rs +++ b/components/net/http_loader.rs @@ -76,13 +76,13 @@ pub struct HttpState { } impl HttpState { - pub fn new() -> HttpState { + pub fn new(certificate_path: &str) -> HttpState { 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())), blocked_content: Arc::new(None), - connector_pool: create_http_connector(), + connector_pool: create_http_connector(certificate_path), } } } diff --git a/components/net/resource_thread.rs b/components/net/resource_thread.rs index 9323635d17e..698f992fcb5 100644 --- a/components/net/resource_thread.rs +++ b/components/net/resource_thread.rs @@ -109,13 +109,13 @@ 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())), - connector: create_http_connector(), + connector: create_http_connector("certs"), }; 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(), + connector: create_http_connector("certs"), }; (resource_group, private_resource_group) } diff --git a/resources/privatekey_for_testing.key b/resources/privatekey_for_testing.key new file mode 100644 index 00000000000..ffaf2dee876 --- /dev/null +++ b/resources/privatekey_for_testing.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC+CuREmlBxE/Ca +amA/y5LJ9RdF4hyJv3/alew/X/x1BiZNdajO1O2VEfIG0iU9terLOg2l8IfuG+Eb +FTOnBIcmGo0vl5OmwEZ1Uhvla+FPqXtOEWEVVnC7/aA+H2GCsp97/2dssMi8//Fl +Mk0UXHvkhjXPO3dwpSiVfIzU2LYXYgua6JFnCG0u629EO61fNF15WoA6seoH1a2t +gTLCsQbapNfUek2T9TCohk2jpkOHxnZNn/KnuM2Anw3N6Ski0Bj+doj/r9xF9CHH +NBng51UMkIGClEJqGj9yzquBd45c09LoG4OAXZKyoQ6q6utidCVYbKh7RaLoHuoq +isg2mUbHAgMBAAECggEBAIIL0/76Flf7DCeu6aReO1nGRSHGRD8i82vyMhOALLMr +/SP+gwDehqH/AL8YKPHcvgpJ9LL8MRiIrXcqAAmnuJAjlT/fGuP+KXj5MivBshIg +aUeX7vZ6C3UpbvFz6fdVInvo365qH0PuZRMZ49MuIn3UNZhVGjvUWTxKWdkBX0IJ +3+aKPEOcx5MInZTr/rNttQq4h898JVM80mzsUBzUzbUgUXJ0vgVyXSBJKGP6PMP2 +pFs/X6QRAvqR69pZ2DarztG7G5EJq2sT2Nymfg4isETiRFM75bPRAmCr+eJQdY7f +jGpxhcTCO0dEP3WgG3M6ZNhtKO4vsm3PhdE06fFdxikCgYEA8GP45n/Yiu272dvX +iKNxYWQ1Yv7A04T7QN8+930/AXIkDw9k86zAstU6Wo47CKseZCKoNMxLO/eD+7tm +SqiMxNEUuxmwb8YYwH/aX27uIdKDahgY6SwLHYFFrBAxU+pm0HVGgLDn6VKPs/db +R31KbJgPr9i2oV1Rt2vha60UIcMCgYEAymH7UEpZ8QRxW1h6lzX/LoTHZHB5t5R5 +UUDR7SErbeM4SpPsJtR2ZuWriW2TAEhBbxgGAGhctLfbdeuYAO+F0PaZlYW/sZx6 +Ei0OWhdd+k/QVj0VHQWCN2oKfjpRj1yYwCcGt7Xei7B0aXVE2A5Aj2bLU+Q9i4x9 +0h3Dac06Uq0CgYA8lLUxQZ7MxETHDoQuxyHXrW1W2WS26Zh4LMqtjD7Imn9D3FlQ +n4SgjOP71kRCVv19ts41IBcFscbtNbj9r6RqJVbYIA063e129cGOs2IH3AmKPzBn +8tWKRf3M8ve7ciMe/a8a13pabpgQfpHeXlDXNSse4bqEyAPD+cgBXsjoCQKBgC1q +jY44ETUADTw1f9U9HdXfoCtO/lGPNSZhyHpRbkCLtA8wYNdZ6HQw6Cy/9TQkAuMe +XgJraRp5A/vTcdoL5li9bjvatujxt4cqq0TWZ5WLobIopPtNSCqNVmt7ROBKJFFC +sMQ7QQTSBV3BHkDp+dz0cX6TAqi1T2r+mOK+Vm9FAoGACIFwOYapBqeDsPwhUiRl +sud+oD28TwbCbQoEVhy5ZoZQZ9S4t7eUaVEYyt/aW1EfeREtvaM5/9DsSmi9f+pw +02XIkZQJplLTDD/0uaCxk0pSJdP9eXkYAEQvEMXs0ING3qIaro2eSVNlO4On9+RY +sz4GyDlleF1ZMsMCTRiNqmg= +-----END PRIVATE KEY----- diff --git a/resources/self_signed_certificate_for_testing.crt b/resources/self_signed_certificate_for_testing.crt new file mode 100644 index 00000000000..db24eb38a9f --- /dev/null +++ b/resources/self_signed_certificate_for_testing.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDlzCCAn+gAwIBAgIJAMVJtbFvDf6vMA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJOQzEQMA4GA1UEBwwHUmFsZWlnaDEQMA4GA1UECgwH +TW96aWxsYTEOMAwGA1UECwwFU2Vydm8xEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0x +NjEyMjMwNTMyMzFaFw0xNzEyMjMwNTMyMzFaMGIxCzAJBgNVBAYTAlVTMQswCQYD +VQQIDAJOQzEQMA4GA1UEBwwHUmFsZWlnaDEQMA4GA1UECgwHTW96aWxsYTEOMAwG +A1UECwwFU2Vydm8xEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAL4K5ESaUHET8JpqYD/Lksn1F0XiHIm/f9qV7D9f/HUG +Jk11qM7U7ZUR8gbSJT216ss6DaXwh+4b4RsVM6cEhyYajS+Xk6bARnVSG+Vr4U+p +e04RYRVWcLv9oD4fYYKyn3v/Z2ywyLz/8WUyTRRce+SGNc87d3ClKJV8jNTYthdi +C5rokWcIbS7rb0Q7rV80XXlagDqx6gfVra2BMsKxBtqk19R6TZP1MKiGTaOmQ4fG +dk2f8qe4zYCfDc3pKSLQGP52iP+v3EX0Icc0GeDnVQyQgYKUQmoaP3LOq4F3jlzT +0ugbg4BdkrKhDqrq62J0JVhsqHtFouge6iqKyDaZRscCAwEAAaNQME4wHQYDVR0O +BBYEFF3/tb9Rfmn4MZ+wepwmZpp/wfkDMB8GA1UdIwQYMBaAFF3/tb9Rfmn4MZ+w +epwmZpp/wfkDMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAD+xK7U/ +21bGNLyadlU/4+IZR1ABe0m8QfWNgwIQZGLGOkaiBg8EGzSuyc01uFv6EfnEBXCX +hEs/cc3JA4LDvGQIgkM8yqEJHsFED2X8sNFs9WiTFM2hCeLwcSNAiJYnOwPXKc+t +ObS5CIFZb2yGfgwv0/zTw7mdQNmdk7LiYlOa9EivvuzG/elT76pijWR5ISKUuOeh +JWmGwZb+XimM5DrCfDQ8cdPSMcnb1Jvkf/Rq1UfnBvvuPmI9XJ2MTnLbn6iwugqE +/+lVNcS8FmPZO1R/jhtU44nKhJvT7FgXuisTPrcTi0WdqjVnQAN3ZeUAFZeVfwan +trAwXF0Zvul1HqE= +-----END CERTIFICATE----- diff --git a/tests/unit/net/fetch.rs b/tests/unit/net/fetch.rs index c88c8b1bf59..07bc5aeea4f 100644 --- a/tests/unit/net/fetch.rs +++ b/tests/unit/net/fetch.rs @@ -18,11 +18,17 @@ use hyper::header::{Encoding, Location, Pragma, Quality, QualityItem, SetCookie, use hyper::header::{Headers, Host, HttpDate, Referer as HyperReferer}; use hyper::method::Method; use hyper::mime::{Mime, SubLevel, TopLevel}; -use hyper::server::{Request as HyperRequest, Response as HyperResponse}; +use hyper::net::Openssl; +use hyper::server::{Request as HyperRequest, Response as HyperResponse, Server}; use hyper::status::StatusCode; use hyper::uri::RequestUri; use msg::constellation_msg::TEST_PIPELINE_ID; use net::fetch::cors_cache::CorsCache; +use net::fetch::methods::FetchContext; +use net::filemanager_thread::FileManager; +use net::hsts::HstsEntry; +use net::test::HttpState; +use net_traits::IncludeSubdomains; use net_traits::NetworkError; use net_traits::ReferrerPolicy; use net_traits::request::{Origin, RedirectMode, Referrer, Request, RequestMode}; @@ -506,6 +512,50 @@ fn test_fetch_with_local_urls_only() { assert!(server_response.is_network_error()); } +#[test] +fn test_fetch_with_hsts() { + static MESSAGE: &'static [u8] = b""; + let handler = move |_: HyperRequest, response: HyperResponse| { + response.send(MESSAGE).unwrap(); + }; + + let path = resources_dir_path().expect("Cannot find resource dir"); + let mut cert_path = path.clone(); + cert_path.push("self_signed_certificate_for_testing.crt"); + + let mut key_path = path.clone(); + key_path.push("privatekey_for_testing.key"); + + let ssl = Openssl::with_cert_and_key(cert_path.into_os_string(), key_path.into_os_string()) + .unwrap(); + + let mut server = Server::https("0.0.0.0:0", ssl).unwrap().handle_threads(handler, 1).unwrap(); + + let context = FetchContext { + state: HttpState::new("self_signed_certificate_for_testing.crt"), + user_agent: DEFAULT_USER_AGENT.into(), + devtools_chan: None, + filemanager: FileManager::new(), + }; + + { + let mut list = context.state.hsts_list.write().unwrap(); + list.push(HstsEntry::new("localhost".to_owned(), IncludeSubdomains::NotIncluded, None) + .unwrap()); + } + let url_string = format!("http://localhost:{}", server.socket.port()); + let url = ServoUrl::parse(&url_string).unwrap(); + let origin = Origin::Origin(url.origin()); + let mut request = Request::new(url, Some(origin), false, None); + *request.referrer.borrow_mut() = Referrer::NoReferrer; + // Set the flag. + request.local_urls_only = false; + let response = fetch_with_context(request, &context); + let _ = server.close(); + assert_eq!(response.internal_response.unwrap().url().unwrap().scheme(), + "https"); +} + fn setup_server_and_fetch(message: &'static [u8], redirect_cap: u32) -> Response { let handler = move |request: HyperRequest, mut response: HyperResponse| { let redirects = match request.uri { diff --git a/tests/unit/net/lib.rs b/tests/unit/net/lib.rs index 6c2f8c7036f..990247e9ab2 100644 --- a/tests/unit/net/lib.rs +++ b/tests/unit/net/lib.rs @@ -55,7 +55,7 @@ struct FetchResponseCollector { fn new_fetch_context(dc: Option>) -> FetchContext { FetchContext { - state: HttpState::new(), + state: HttpState::new("certs"), user_agent: DEFAULT_USER_AGENT.into(), devtools_chan: dc, filemanager: FileManager::new(),