From 15c90a58b20a085f673dcb543ce8ae850bfc2f1b Mon Sep 17 00:00:00 2001 From: Sam Gibson Date: Tue, 23 Jun 2015 11:57:28 -0700 Subject: [PATCH] Expire HSTS entries that have exceeded their max-age servo/servo#6105 --- components/net/resource_task.rs | 18 +++++++++--- components/servo/Cargo.lock | 1 + tests/unit/net/Cargo.toml | 1 + tests/unit/net/lib.rs | 1 + tests/unit/net/resource_task.rs | 51 +++++++++++++++++++++++++++++++++ 5 files changed, 68 insertions(+), 4 deletions(-) diff --git a/components/net/resource_task.rs b/components/net/resource_task.rs index 94277ba1c36..a5f795db898 100644 --- a/components/net/resource_task.rs +++ b/components/net/resource_task.rs @@ -194,7 +194,7 @@ pub struct HSTSEntry { pub host: String, pub include_subdomains: bool, pub max_age: Option, - timestamp: Option + pub timestamp: Option } impl HSTSEntry { @@ -211,12 +211,22 @@ impl HSTSEntry { } } + pub fn is_expired(&self) -> bool { + match (self.max_age, self.timestamp) { + (Some(max_age), Some(timestamp)) => { + (time::get_time().sec as u64) - timestamp > max_age + }, + + _ => false + } + } + fn matches_domain(&self, host: &str) -> bool { - self.host == host + !self.is_expired() && self.host == host } fn matches_subdomain(&self, host: &str) -> bool { - host.ends_with(&format!(".{}", self.host)) + !self.is_expired() && host.ends_with(&format!(".{}", self.host)) } } @@ -254,7 +264,7 @@ impl HSTSList { }) } - pub fn has_subdomain(&self, host: String) -> bool { + fn has_subdomain(&self, host: String) -> bool { self.entries.iter().any(|e| { e.matches_subdomain(&host) }) diff --git a/components/servo/Cargo.lock b/components/servo/Cargo.lock index 568a41f5e7f..3c87137b941 100644 --- a/components/servo/Cargo.lock +++ b/components/servo/Cargo.lock @@ -876,6 +876,7 @@ dependencies = [ "net 0.0.1", "net_traits 0.0.1", "url 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", "util 0.0.1", ] diff --git a/tests/unit/net/Cargo.toml b/tests/unit/net/Cargo.toml index eb2bffaac4d..5e9608356c7 100644 --- a/tests/unit/net/Cargo.toml +++ b/tests/unit/net/Cargo.toml @@ -21,3 +21,4 @@ path = "../../../components/util" cookie = "0.1" hyper = "0.6" url = "0.2" +time = "0.1" diff --git a/tests/unit/net/lib.rs b/tests/unit/net/lib.rs index f9c11c958a3..335cbd721a6 100644 --- a/tests/unit/net/lib.rs +++ b/tests/unit/net/lib.rs @@ -8,6 +8,7 @@ extern crate net; extern crate net_traits; extern crate url; extern crate util; +extern crate time; #[cfg(test)] mod cookie; #[cfg(test)] mod data_loader; diff --git a/tests/unit/net/resource_task.rs b/tests/unit/net/resource_task.rs index ecf4bea40d4..50ea0780347 100644 --- a/tests/unit/net/resource_task.rs +++ b/tests/unit/net/resource_task.rs @@ -11,6 +11,7 @@ use std::borrow::ToOwned; use std::collections::HashMap; use std::sync::mpsc::channel; use url::Url; +use time; #[test] @@ -19,6 +20,42 @@ fn test_exit() { resource_task.send(ControlMsg::Exit).unwrap(); } +#[test] +fn test_hsts_entry_is_not_expired_when_it_has_no_timestamp() { + let entry = HSTSEntry { + host: "mozilla.org".to_string(), + include_subdomains: false, + max_age: Some(20), + timestamp: None + }; + + assert!(!entry.is_expired()); +} + +#[test] +fn test_hsts_entry_is_not_expired_when_it_has_no_max_age() { + let entry = HSTSEntry { + host: "mozilla.org".to_string(), + include_subdomains: false, + max_age: None, + timestamp: Some(time::get_time().sec as u64) + }; + + assert!(!entry.is_expired()); +} + +#[test] +fn test_hsts_entry_is_expired_when_it_has_reached_its_max_age() { + let entry = HSTSEntry { + host: "mozilla.org".to_string(), + include_subdomains: false, + max_age: Some(10), + timestamp: Some(time::get_time().sec as u64 - 20u64) + }; + + assert!(entry.is_expired()); +} + #[test] fn test_hsts_entry_cant_be_created_with_ipv6_address_as_host() { let entry = HSTSEntry::new( @@ -178,6 +215,20 @@ fn test_hsts_list_with_subdomain_when_host_is_exact_match_is_always_secure() { assert!(hsts_list.always_secure("mozilla.org") == true); } +#[test] +fn test_hsts_list_with_expired_entry_is_not_always_secure() { + let hsts_list = HSTSList { + entries: vec![HSTSEntry { + host: "mozilla.org".to_string(), + include_subdomains: false, + max_age: Some(20), + timestamp: Some(time::get_time().sec as u64 - 100u64) + }] + }; + + assert!(!hsts_list.always_secure("mozilla.org")); +} + #[test] fn test_make_hsts_secure_does_not_change_explicit_port() { let load_data = LoadData::new(Url::parse("http://mozilla.org:8080/").unwrap(), None);