servo: Merge #14716 - Implement HSTS fetch step (from mrnayak:hsts); r=jdm
authorRaghav <rmuddur@gmail.com>
Thu, 29 Dec 2016 00:35:09 -0800
changeset 478611 fb3cb6e8f720e2772efba1fae1ea4dc87b8e8a72
parent 478610 ca6fed9033979a068f0bb4cd2838fc455b0b781d
child 478612 3965ac1953b2e89bd21740a91c0d9a6eaaddf135
push id44079
push userbmo:gps@mozilla.com
push dateSat, 04 Feb 2017 00:14:49 +0000
reviewersjdm
servo: Merge #14716 - Implement HSTS fetch step (from mrnayak:hsts); r=jdm 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. This should fix https://github.com/servo/servo/issues/14363 <!-- Please describe your changes on the following line: --> --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #14363 <!-- Either: --> - [X] There are tests for these changes <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: c7991d596f7453d09c2b2a98eecce72f182a4e24
servo/components/net/connector.rs
servo/components/net/fetch/methods.rs
servo/components/net/http_loader.rs
servo/components/net/resource_thread.rs
servo/resources/privatekey_for_testing.key
servo/resources/self_signed_certificate_for_testing.crt
servo/tests/unit/net/fetch.rs
servo/tests/unit/net/lib.rs
--- a/servo/components/net/connector.rs
+++ b/servo/components/net/connector.rs
@@ -22,21 +22,21 @@ const DEFAULT_CIPHERS: &'static str = co
     "ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:",
     "ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA:",
     "ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:",
     "DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:",
     "ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:",
     "AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA"
 );
 
-pub fn create_http_connector() -> Arc<Pool<Connector>> {
+pub fn create_http_connector(certificate_file: &str) -> Arc<Pool<Connector>> {
     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 {
         context: Arc::new(context)
     });
 
     Arc::new(Pool::with_connector(Default::default(), connector))
 }
--- a/servo/components/net/fetch/methods.rs
+++ b/servo/components/net/fetch/methods.rs
@@ -184,17 +184,25 @@ pub fn main_fetch(request: Rc<Request>,
             }
         };
         if let Some(referrer_url) = referrer_url {
             *referrer = Referrer::ReferrerUrl(referrer_url);
         }
     }
 
     // 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
 
     // Step 11
     let response = match response {
         Some(response) => response,
         None => {
--- a/servo/components/net/http_loader.rs
+++ b/servo/components/net/http_loader.rs
@@ -71,23 +71,23 @@ pub struct HttpState {
     pub hsts_list: Arc<RwLock<HstsList>>,
     pub cookie_jar: Arc<RwLock<CookieStorage>>,
     pub auth_cache: Arc<RwLock<AuthCache>>,
     pub blocked_content: Arc<Option<RuleList>>,
     pub connector_pool: Arc<Pool<Connector>>,
 }
 
 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),
         }
     }
 }
 
 fn precise_time_ms() -> u64 {
     time::precise_time_ns() / (1000 * 1000)
 }
 
--- a/servo/components/net/resource_thread.rs
+++ b/servo/components/net/resource_thread.rs
@@ -104,23 +104,23 @@ fn create_resource_groups(config_dir: Op
         read_json_from_file(&mut auth_cache, config_dir, "auth_cache.json");
         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 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(),
+        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)
 }
 
 impl ResourceChannelManager {
     #[allow(unsafe_code)]
     fn start(&mut self,
              public_receiver: IpcReceiver<CoreResourceMsg>,
new file mode 100644
--- /dev/null
+++ b/servo/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-----
new file mode 100644
--- /dev/null
+++ b/servo/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-----
--- a/servo/tests/unit/net/fetch.rs
+++ b/servo/tests/unit/net/fetch.rs
@@ -13,21 +13,27 @@ use hyper::LanguageTag;
 use hyper::header::{Accept, AccessControlAllowCredentials, AccessControlAllowHeaders, AccessControlAllowOrigin};
 use hyper::header::{AcceptEncoding, AcceptLanguage, AccessControlAllowMethods, AccessControlMaxAge};
 use hyper::header::{AccessControlRequestHeaders, AccessControlRequestMethod, Date, UserAgent};
 use hyper::header::{CacheControl, ContentLanguage, ContentLength, ContentType, Expires, LastModified};
 use hyper::header::{Encoding, Location, Pragma, Quality, QualityItem, SetCookie, qitem};
 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};
 use net_traits::response::{CacheState, Response, ResponseBody, ResponseType};
 use servo_config::resource_files::resources_dir_path;
 use servo_url::ServoUrl;
 use std::fs::File;
 use std::io::Read;
@@ -501,16 +507,60 @@ fn test_fetch_with_local_urls_only() {
     let server_response = do_fetch(server_url);
 
     let _ = server.close();
 
     assert!(!local_response.is_network_error());
     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 {
             RequestUri::AbsolutePath(url) =>
                 url.split("/").collect::<String>().parse::<u32>().unwrap_or(0),
             RequestUri::AbsoluteUri(url) =>
                 url.path_segments().unwrap().next_back().unwrap().parse::<u32>().unwrap_or(0),
             _ => panic!()
--- a/servo/tests/unit/net/lib.rs
+++ b/servo/tests/unit/net/lib.rs
@@ -50,17 +50,17 @@ use std::sync::mpsc::{Sender, channel};
 const DEFAULT_USER_AGENT: &'static str = "Such Browser. Very Layout. Wow.";
 
 struct FetchResponseCollector {
     sender: Sender<Response>,
 }
 
 fn new_fetch_context(dc: Option<Sender<DevtoolsControlMsg>>) -> FetchContext {
     FetchContext {
-        state: HttpState::new(),
+        state: HttpState::new("certs"),
         user_agent: DEFAULT_USER_AGENT.into(),
         devtools_chan: dc,
         filemanager: FileManager::new(),
     }
 }
 impl FetchTaskTarget for FetchResponseCollector {
     fn process_request_body(&mut self, _: &Request) {}
     fn process_request_eof(&mut self, _: &Request) {}