1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/security/manager/boot/src/nsSiteSecurityService.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,755 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +#include "plstr.h" 1.9 +#include "prlog.h" 1.10 +#include "prprf.h" 1.11 +#include "prnetdb.h" 1.12 +#include "nsCRTGlue.h" 1.13 +#include "nsIPermissionManager.h" 1.14 +#include "nsISSLStatus.h" 1.15 +#include "nsISSLStatusProvider.h" 1.16 +#include "nsSiteSecurityService.h" 1.17 +#include "nsIURI.h" 1.18 +#include "nsNetUtil.h" 1.19 +#include "nsThreadUtils.h" 1.20 +#include "nsString.h" 1.21 +#include "nsIScriptSecurityManager.h" 1.22 +#include "nsISocketProvider.h" 1.23 +#include "mozilla/Preferences.h" 1.24 +#include "mozilla/LinkedList.h" 1.25 +#include "nsSecurityHeaderParser.h" 1.26 + 1.27 +// A note about the preload list: 1.28 +// When a site specifically disables sts by sending a header with 1.29 +// 'max-age: 0', we keep a "knockout" value that means "we have no information 1.30 +// regarding the sts state of this host" (any ancestor of "this host" can still 1.31 +// influence its sts status via include subdomains, however). 1.32 +// This prevents the preload list from overriding the site's current 1.33 +// desired sts status. Knockout values are indicated by permission values of 1.34 +// STS_KNOCKOUT. 1.35 +#include "nsSTSPreloadList.inc" 1.36 + 1.37 +#define STS_SET (nsIPermissionManager::ALLOW_ACTION) 1.38 +#define STS_UNSET (nsIPermissionManager::UNKNOWN_ACTION) 1.39 +#define STS_KNOCKOUT (nsIPermissionManager::DENY_ACTION) 1.40 + 1.41 +#if defined(PR_LOGGING) 1.42 +static PRLogModuleInfo * 1.43 +GetSSSLog() 1.44 +{ 1.45 + static PRLogModuleInfo *gSSSLog; 1.46 + if (!gSSSLog) 1.47 + gSSSLog = PR_NewLogModule("nsSSService"); 1.48 + return gSSSLog; 1.49 +} 1.50 +#endif 1.51 + 1.52 +#define SSSLOG(args) PR_LOG(GetSSSLog(), 4, args) 1.53 + 1.54 +//////////////////////////////////////////////////////////////////////////////// 1.55 + 1.56 +nsSSSHostEntry::nsSSSHostEntry(const char* aHost) 1.57 + : mHost(aHost) 1.58 + , mExpireTime(0) 1.59 + , mStsPermission(STS_UNSET) 1.60 + , mExpired(false) 1.61 + , mIncludeSubdomains(false) 1.62 +{ 1.63 +} 1.64 + 1.65 +nsSSSHostEntry::nsSSSHostEntry(const nsSSSHostEntry& toCopy) 1.66 + : mHost(toCopy.mHost) 1.67 + , mExpireTime(toCopy.mExpireTime) 1.68 + , mStsPermission(toCopy.mStsPermission) 1.69 + , mExpired(toCopy.mExpired) 1.70 + , mIncludeSubdomains(toCopy.mIncludeSubdomains) 1.71 +{ 1.72 +} 1.73 + 1.74 +//////////////////////////////////////////////////////////////////////////////// 1.75 + 1.76 + 1.77 +nsSiteSecurityService::nsSiteSecurityService() 1.78 + : mUsePreloadList(true) 1.79 +{ 1.80 +} 1.81 + 1.82 +nsSiteSecurityService::~nsSiteSecurityService() 1.83 +{ 1.84 +} 1.85 + 1.86 +NS_IMPL_ISUPPORTS(nsSiteSecurityService, 1.87 + nsIObserver, 1.88 + nsISiteSecurityService) 1.89 + 1.90 +nsresult 1.91 +nsSiteSecurityService::Init() 1.92 +{ 1.93 + nsresult rv; 1.94 + 1.95 + mPermMgr = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); 1.96 + NS_ENSURE_SUCCESS(rv, rv); 1.97 + 1.98 + mUsePreloadList = mozilla::Preferences::GetBool("network.stricttransportsecurity.preloadlist", true); 1.99 + mozilla::Preferences::AddStrongObserver(this, "network.stricttransportsecurity.preloadlist"); 1.100 + mObserverService = mozilla::services::GetObserverService(); 1.101 + if (mObserverService) 1.102 + mObserverService->AddObserver(this, "last-pb-context-exited", false); 1.103 + 1.104 + return NS_OK; 1.105 +} 1.106 + 1.107 +nsresult 1.108 +nsSiteSecurityService::GetHost(nsIURI *aURI, nsACString &aResult) 1.109 +{ 1.110 + nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI); 1.111 + if (!innerURI) return NS_ERROR_FAILURE; 1.112 + 1.113 + nsresult rv = innerURI->GetAsciiHost(aResult); 1.114 + 1.115 + if (NS_FAILED(rv) || aResult.IsEmpty()) 1.116 + return NS_ERROR_UNEXPECTED; 1.117 + 1.118 + return NS_OK; 1.119 +} 1.120 + 1.121 +nsresult 1.122 +nsSiteSecurityService::GetPrincipalForURI(nsIURI* aURI, 1.123 + nsIPrincipal** aPrincipal) 1.124 +{ 1.125 + nsresult rv; 1.126 + nsCOMPtr<nsIScriptSecurityManager> securityManager = 1.127 + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); 1.128 + NS_ENSURE_SUCCESS(rv, rv); 1.129 + 1.130 + // We have to normalize the scheme of the URIs we're using, so just use https. 1.131 + // HSTS information is shared across all ports for a given host. 1.132 + nsAutoCString host; 1.133 + rv = GetHost(aURI, host); 1.134 + NS_ENSURE_SUCCESS(rv, rv); 1.135 + nsCOMPtr<nsIURI> uri; 1.136 + rv = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://") + host); 1.137 + NS_ENSURE_SUCCESS(rv, rv); 1.138 + 1.139 + // We want all apps to share HSTS state, so this is one of the few places 1.140 + // where we do not silo persistent state by extended origin. 1.141 + return securityManager->GetNoAppCodebasePrincipal(uri, aPrincipal); 1.142 +} 1.143 + 1.144 +nsresult 1.145 +nsSiteSecurityService::SetState(uint32_t aType, 1.146 + nsIURI* aSourceURI, 1.147 + int64_t maxage, 1.148 + bool includeSubdomains, 1.149 + uint32_t flags) 1.150 +{ 1.151 + // If max-age is zero, that's an indication to immediately remove the 1.152 + // permissions, so here's a shortcut. 1.153 + if (!maxage) { 1.154 + return RemoveState(aType, aSourceURI, flags); 1.155 + } 1.156 + 1.157 + // Expire time is millis from now. Since STS max-age is in seconds, and 1.158 + // PR_Now() is in micros, must equalize the units at milliseconds. 1.159 + int64_t expiretime = (PR_Now() / PR_USEC_PER_MSEC) + 1.160 + (maxage * PR_MSEC_PER_SEC); 1.161 + 1.162 + bool isPrivate = flags & nsISocketProvider::NO_PERMANENT_STORAGE; 1.163 + 1.164 + // record entry for this host with max-age in the permissions manager 1.165 + SSSLOG(("SSS: maxage permission SET, adding permission\n")); 1.166 + nsresult rv = AddPermission(aSourceURI, 1.167 + STS_PERMISSION, 1.168 + (uint32_t) STS_SET, 1.169 + (uint32_t) nsIPermissionManager::EXPIRE_TIME, 1.170 + expiretime, 1.171 + isPrivate); 1.172 + NS_ENSURE_SUCCESS(rv, rv); 1.173 + 1.174 + if (includeSubdomains) { 1.175 + // record entry for this host with include subdomains in the permissions manager 1.176 + SSSLOG(("SSS: subdomains permission SET, adding permission\n")); 1.177 + rv = AddPermission(aSourceURI, 1.178 + STS_SUBDOMAIN_PERMISSION, 1.179 + (uint32_t) STS_SET, 1.180 + (uint32_t) nsIPermissionManager::EXPIRE_TIME, 1.181 + expiretime, 1.182 + isPrivate); 1.183 + NS_ENSURE_SUCCESS(rv, rv); 1.184 + } else { // !includeSubdomains 1.185 + nsAutoCString hostname; 1.186 + rv = GetHost(aSourceURI, hostname); 1.187 + NS_ENSURE_SUCCESS(rv, rv); 1.188 + 1.189 + SSSLOG(("SSS: subdomains permission UNSET, removing any existing ones\n")); 1.190 + rv = RemovePermission(hostname, STS_SUBDOMAIN_PERMISSION, isPrivate); 1.191 + NS_ENSURE_SUCCESS(rv, rv); 1.192 + } 1.193 + return NS_OK; 1.194 +} 1.195 + 1.196 +NS_IMETHODIMP 1.197 +nsSiteSecurityService::RemoveState(uint32_t aType, nsIURI* aURI, uint32_t aFlags) 1.198 +{ 1.199 + // Should be called on the main thread (or via proxy) since the permission 1.200 + // manager is used and it's not threadsafe. 1.201 + NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED); 1.202 + // Only HSTS is supported at the moment. 1.203 + NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS, 1.204 + NS_ERROR_NOT_IMPLEMENTED); 1.205 + 1.206 + nsAutoCString hostname; 1.207 + nsresult rv = GetHost(aURI, hostname); 1.208 + NS_ENSURE_SUCCESS(rv, rv); 1.209 + 1.210 + bool isPrivate = aFlags & nsISocketProvider::NO_PERMANENT_STORAGE; 1.211 + 1.212 + rv = RemovePermission(hostname, STS_PERMISSION, isPrivate); 1.213 + NS_ENSURE_SUCCESS(rv, rv); 1.214 + SSSLOG(("SSS: deleted maxage permission\n")); 1.215 + 1.216 + rv = RemovePermission(hostname, STS_SUBDOMAIN_PERMISSION, isPrivate); 1.217 + NS_ENSURE_SUCCESS(rv, rv); 1.218 + SSSLOG(("SSS: deleted subdomains permission\n")); 1.219 + 1.220 + return NS_OK; 1.221 +} 1.222 + 1.223 +static bool 1.224 +HostIsIPAddress(const char *hostname) 1.225 +{ 1.226 + PRNetAddr hostAddr; 1.227 + return (PR_StringToNetAddr(hostname, &hostAddr) == PR_SUCCESS); 1.228 +} 1.229 + 1.230 +NS_IMETHODIMP 1.231 +nsSiteSecurityService::ProcessHeader(uint32_t aType, 1.232 + nsIURI* aSourceURI, 1.233 + const char* aHeader, 1.234 + uint32_t aFlags, 1.235 + uint64_t *aMaxAge, 1.236 + bool *aIncludeSubdomains) 1.237 +{ 1.238 + // Should be called on the main thread (or via proxy) since the permission 1.239 + // manager is used and it's not threadsafe. 1.240 + NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED); 1.241 + // Only HSTS is supported at the moment. 1.242 + NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS, 1.243 + NS_ERROR_NOT_IMPLEMENTED); 1.244 + 1.245 + if (aMaxAge != nullptr) { 1.246 + *aMaxAge = 0; 1.247 + } 1.248 + 1.249 + if (aIncludeSubdomains != nullptr) { 1.250 + *aIncludeSubdomains = false; 1.251 + } 1.252 + 1.253 + nsAutoCString host; 1.254 + nsresult rv = GetHost(aSourceURI, host); 1.255 + NS_ENSURE_SUCCESS(rv, rv); 1.256 + if (HostIsIPAddress(host.get())) { 1.257 + /* Don't process headers if a site is accessed by IP address. */ 1.258 + return NS_OK; 1.259 + } 1.260 + 1.261 + char * header = NS_strdup(aHeader); 1.262 + if (!header) return NS_ERROR_OUT_OF_MEMORY; 1.263 + rv = ProcessHeaderMutating(aType, aSourceURI, header, aFlags, 1.264 + aMaxAge, aIncludeSubdomains); 1.265 + NS_Free(header); 1.266 + return rv; 1.267 +} 1.268 + 1.269 +nsresult 1.270 +nsSiteSecurityService::ProcessHeaderMutating(uint32_t aType, 1.271 + nsIURI* aSourceURI, 1.272 + char* aHeader, 1.273 + uint32_t aFlags, 1.274 + uint64_t *aMaxAge, 1.275 + bool *aIncludeSubdomains) 1.276 +{ 1.277 + SSSLOG(("SSS: processing header '%s'", aHeader)); 1.278 + 1.279 + // "Strict-Transport-Security" ":" OWS 1.280 + // STS-d *( OWS ";" OWS STS-d OWS) 1.281 + // 1.282 + // ; STS directive 1.283 + // STS-d = maxAge / includeSubDomains 1.284 + // 1.285 + // maxAge = "max-age" "=" delta-seconds v-ext 1.286 + // 1.287 + // includeSubDomains = [ "includeSubDomains" ] 1.288 + // 1.289 + // The order of the directives is not significant. 1.290 + // All directives must appear only once. 1.291 + // Directive names are case-insensitive. 1.292 + // The entire header is invalid if a directive not conforming to the 1.293 + // syntax is encountered. 1.294 + // Unrecognized directives (that are otherwise syntactically valid) are 1.295 + // ignored, and the rest of the header is parsed as normal. 1.296 + 1.297 + bool foundMaxAge = false; 1.298 + bool foundIncludeSubdomains = false; 1.299 + bool foundUnrecognizedDirective = false; 1.300 + int64_t maxAge = 0; 1.301 + 1.302 + NS_NAMED_LITERAL_CSTRING(max_age_var, "max-age"); 1.303 + NS_NAMED_LITERAL_CSTRING(include_subd_var, "includesubdomains"); 1.304 + 1.305 + 1.306 + nsSecurityHeaderParser parser(aHeader); 1.307 + nsresult rv = parser.Parse(); 1.308 + if (NS_FAILED(rv)) { 1.309 + SSSLOG(("SSS: could not parse header")); 1.310 + return rv; 1.311 + } 1.312 + mozilla::LinkedList<nsSecurityHeaderDirective> *directives = parser.GetDirectives(); 1.313 + 1.314 + for (nsSecurityHeaderDirective *directive = directives->getFirst(); 1.315 + directive != nullptr; directive = directive->getNext()) { 1.316 + if (directive->mName.Length() == max_age_var.Length() && 1.317 + directive->mName.EqualsIgnoreCase(max_age_var.get(), 1.318 + max_age_var.Length())) { 1.319 + if (foundMaxAge) { 1.320 + SSSLOG(("SSS: found two max-age directives")); 1.321 + return NS_ERROR_FAILURE; 1.322 + } 1.323 + 1.324 + SSSLOG(("SSS: found max-age directive")); 1.325 + foundMaxAge = true; 1.326 + 1.327 + size_t len = directive->mValue.Length(); 1.328 + for (size_t i = 0; i < len; i++) { 1.329 + char chr = directive->mValue.CharAt(i); 1.330 + if (chr < '0' || chr > '9') { 1.331 + SSSLOG(("SSS: invalid value for max-age directive")); 1.332 + return NS_ERROR_FAILURE; 1.333 + } 1.334 + } 1.335 + 1.336 + if (PR_sscanf(directive->mValue.get(), "%lld", &maxAge) != 1) { 1.337 + SSSLOG(("SSS: could not parse delta-seconds")); 1.338 + return NS_ERROR_FAILURE; 1.339 + } 1.340 + 1.341 + SSSLOG(("SSS: parsed delta-seconds: %lld", maxAge)); 1.342 + } else if (directive->mName.Length() == include_subd_var.Length() && 1.343 + directive->mName.EqualsIgnoreCase(include_subd_var.get(), 1.344 + include_subd_var.Length())) { 1.345 + if (foundIncludeSubdomains) { 1.346 + SSSLOG(("SSS: found two includeSubdomains directives")); 1.347 + return NS_ERROR_FAILURE; 1.348 + } 1.349 + 1.350 + SSSLOG(("SSS: found includeSubdomains directive")); 1.351 + foundIncludeSubdomains = true; 1.352 + 1.353 + if (directive->mValue.Length() != 0) { 1.354 + SSSLOG(("SSS: includeSubdomains directive unexpectedly had value '%s'", directive->mValue.get())); 1.355 + return NS_ERROR_FAILURE; 1.356 + } 1.357 + } else { 1.358 + SSSLOG(("SSS: ignoring unrecognized directive '%s'", directive->mName.get())); 1.359 + foundUnrecognizedDirective = true; 1.360 + } 1.361 + } 1.362 + 1.363 + // after processing all the directives, make sure we came across max-age 1.364 + // somewhere. 1.365 + if (!foundMaxAge) { 1.366 + SSSLOG(("SSS: did not encounter required max-age directive")); 1.367 + return NS_ERROR_FAILURE; 1.368 + } 1.369 + 1.370 + // record the successfully parsed header data. 1.371 + SetState(aType, aSourceURI, maxAge, foundIncludeSubdomains, aFlags); 1.372 + 1.373 + if (aMaxAge != nullptr) { 1.374 + *aMaxAge = (uint64_t)maxAge; 1.375 + } 1.376 + 1.377 + if (aIncludeSubdomains != nullptr) { 1.378 + *aIncludeSubdomains = foundIncludeSubdomains; 1.379 + } 1.380 + 1.381 + return foundUnrecognizedDirective ? 1.382 + NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA : 1.383 + NS_OK; 1.384 +} 1.385 + 1.386 +NS_IMETHODIMP 1.387 +nsSiteSecurityService::IsSecureHost(uint32_t aType, const char* aHost, 1.388 + uint32_t aFlags, bool* aResult) 1.389 +{ 1.390 + // Should be called on the main thread (or via proxy) since the permission 1.391 + // manager is used and it's not threadsafe. 1.392 + NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED); 1.393 + // Only HSTS is supported at the moment. 1.394 + NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS, 1.395 + NS_ERROR_NOT_IMPLEMENTED); 1.396 + 1.397 + /* An IP address never qualifies as a secure URI. */ 1.398 + if (HostIsIPAddress(aHost)) { 1.399 + *aResult = false; 1.400 + return NS_OK; 1.401 + } 1.402 + 1.403 + nsCOMPtr<nsIURI> uri; 1.404 + nsDependentCString hostString(aHost); 1.405 + nsresult rv = NS_NewURI(getter_AddRefs(uri), 1.406 + NS_LITERAL_CSTRING("https://") + hostString); 1.407 + NS_ENSURE_SUCCESS(rv, rv); 1.408 + return IsSecureURI(aType, uri, aFlags, aResult); 1.409 +} 1.410 + 1.411 +int STSPreloadCompare(const void *key, const void *entry) 1.412 +{ 1.413 + const char *keyStr = (const char *)key; 1.414 + const nsSTSPreload *preloadEntry = (const nsSTSPreload *)entry; 1.415 + return strcmp(keyStr, preloadEntry->mHost); 1.416 +} 1.417 + 1.418 +// Returns the preload list entry for the given host, if it exists. 1.419 +// Only does exact host matching - the user must decide how to use the returned 1.420 +// data. May return null. 1.421 +const nsSTSPreload * 1.422 +nsSiteSecurityService::GetPreloadListEntry(const char *aHost) 1.423 +{ 1.424 + PRTime currentTime = PR_Now(); 1.425 + int32_t timeOffset = 0; 1.426 + nsresult rv = mozilla::Preferences::GetInt("test.currentTimeOffsetSeconds", 1.427 + &timeOffset); 1.428 + if (NS_SUCCEEDED(rv)) { 1.429 + currentTime += (PRTime(timeOffset) * PR_USEC_PER_SEC); 1.430 + } 1.431 + 1.432 + if (mUsePreloadList && currentTime < gPreloadListExpirationTime) { 1.433 + return (const nsSTSPreload *) bsearch(aHost, 1.434 + kSTSPreloadList, 1.435 + mozilla::ArrayLength(kSTSPreloadList), 1.436 + sizeof(nsSTSPreload), 1.437 + STSPreloadCompare); 1.438 + } 1.439 + 1.440 + return nullptr; 1.441 +} 1.442 + 1.443 +NS_IMETHODIMP 1.444 +nsSiteSecurityService::IsSecureURI(uint32_t aType, nsIURI* aURI, 1.445 + uint32_t aFlags, bool* aResult) 1.446 +{ 1.447 + // Should be called on the main thread (or via proxy) since the permission 1.448 + // manager is used and it's not threadsafe. 1.449 + NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED); 1.450 + // Only HSTS is supported at the moment. 1.451 + NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS, 1.452 + NS_ERROR_NOT_IMPLEMENTED); 1.453 + 1.454 + // set default in case if we can't find any STS information 1.455 + *aResult = false; 1.456 + 1.457 + nsAutoCString host; 1.458 + nsresult rv = GetHost(aURI, host); 1.459 + NS_ENSURE_SUCCESS(rv, rv); 1.460 + 1.461 + /* An IP address never qualifies as a secure URI. */ 1.462 + if (HostIsIPAddress(host.BeginReading())) { 1.463 + return NS_OK; 1.464 + } 1.465 + 1.466 + // Holepunch chart.apis.google.com and subdomains. 1.467 + if (host.Equals(NS_LITERAL_CSTRING("chart.apis.google.com")) || 1.468 + StringEndsWith(host, NS_LITERAL_CSTRING(".chart.apis.google.com"))) { 1.469 + return NS_OK; 1.470 + } 1.471 + 1.472 + const nsSTSPreload *preload = nullptr; 1.473 + nsSSSHostEntry *pbEntry = nullptr; 1.474 + 1.475 + bool isPrivate = aFlags & nsISocketProvider::NO_PERMANENT_STORAGE; 1.476 + if (isPrivate) { 1.477 + pbEntry = mPrivateModeHostTable.GetEntry(host.get()); 1.478 + } 1.479 + 1.480 + nsCOMPtr<nsIPrincipal> principal; 1.481 + rv = GetPrincipalForURI(aURI, getter_AddRefs(principal)); 1.482 + NS_ENSURE_SUCCESS(rv, rv); 1.483 + 1.484 + uint32_t permMgrPermission; 1.485 + rv = mPermMgr->TestExactPermissionFromPrincipal(principal, STS_PERMISSION, 1.486 + &permMgrPermission); 1.487 + NS_ENSURE_SUCCESS(rv, rv); 1.488 + 1.489 + // First check the exact host. This involves first checking for an entry in 1.490 + // the private browsing table. If that entry exists, we don't want to check 1.491 + // in either the permission manager or the preload list. We only want to use 1.492 + // the stored permission if it is not a knockout entry, however. 1.493 + // Additionally, if it is a knockout entry, we want to stop looking for data 1.494 + // on the host, because the knockout entry indicates "we have no information 1.495 + // regarding the sts status of this host". 1.496 + if (pbEntry && pbEntry->mStsPermission != STS_UNSET) { 1.497 + SSSLOG(("Found private browsing table entry for %s", host.get())); 1.498 + if (!pbEntry->IsExpired() && pbEntry->mStsPermission == STS_SET) { 1.499 + *aResult = true; 1.500 + return NS_OK; 1.501 + } 1.502 + } 1.503 + // Next we look in the permission manager. Same story here regarding 1.504 + // knockout entries. 1.505 + else if (permMgrPermission != STS_UNSET) { 1.506 + SSSLOG(("Found permission manager entry for %s", host.get())); 1.507 + if (permMgrPermission == STS_SET) { 1.508 + *aResult = true; 1.509 + return NS_OK; 1.510 + } 1.511 + } 1.512 + // Finally look in the preloaded list. This is the exact host, 1.513 + // so if an entry exists at all, this host is sts. 1.514 + else if (GetPreloadListEntry(host.get())) { 1.515 + SSSLOG(("%s is a preloaded STS host", host.get())); 1.516 + *aResult = true; 1.517 + return NS_OK; 1.518 + } 1.519 + 1.520 + // Used for testing permissions as we walk up the domain tree. 1.521 + nsCOMPtr<nsIURI> domainWalkURI; 1.522 + nsCOMPtr<nsIPrincipal> domainWalkPrincipal; 1.523 + const char *subdomain; 1.524 + 1.525 + SSSLOG(("no HSTS data for %s found, walking up domain", host.get())); 1.526 + uint32_t offset = 0; 1.527 + for (offset = host.FindChar('.', offset) + 1; 1.528 + offset > 0; 1.529 + offset = host.FindChar('.', offset) + 1) { 1.530 + 1.531 + subdomain = host.get() + offset; 1.532 + 1.533 + // If we get an empty string, don't continue. 1.534 + if (strlen(subdomain) < 1) { 1.535 + break; 1.536 + } 1.537 + 1.538 + if (isPrivate) { 1.539 + pbEntry = mPrivateModeHostTable.GetEntry(subdomain); 1.540 + } 1.541 + 1.542 + // normalize all URIs with https:// 1.543 + rv = NS_NewURI(getter_AddRefs(domainWalkURI), 1.544 + NS_LITERAL_CSTRING("https://") + Substring(host, offset)); 1.545 + NS_ENSURE_SUCCESS(rv, rv); 1.546 + 1.547 + rv = GetPrincipalForURI(domainWalkURI, getter_AddRefs(domainWalkPrincipal)); 1.548 + NS_ENSURE_SUCCESS(rv, rv); 1.549 + 1.550 + rv = mPermMgr->TestExactPermissionFromPrincipal(domainWalkPrincipal, 1.551 + STS_PERMISSION, 1.552 + &permMgrPermission); 1.553 + NS_ENSURE_SUCCESS(rv, rv); 1.554 + 1.555 + // Do the same thing as with the exact host, except now we're looking at 1.556 + // ancestor domains of the original host. So, we have to look at the 1.557 + // include subdomains permissions (although we still have to check for the 1.558 + // STS_PERMISSION first to check that this is an sts host and not a 1.559 + // knockout entry - and again, if it is a knockout entry, we stop looking 1.560 + // for data on it and skip to the next higher up ancestor domain). 1.561 + if (pbEntry && pbEntry->mStsPermission != STS_UNSET) { 1.562 + SSSLOG(("Found private browsing table entry for %s", subdomain)); 1.563 + if (!pbEntry->IsExpired() && pbEntry->mStsPermission == STS_SET) { 1.564 + *aResult = pbEntry->mIncludeSubdomains; 1.565 + break; 1.566 + } 1.567 + } 1.568 + else if (permMgrPermission != STS_UNSET) { 1.569 + SSSLOG(("Found permission manager entry for %s", subdomain)); 1.570 + if (permMgrPermission == STS_SET) { 1.571 + uint32_t subdomainPermission; 1.572 + rv = mPermMgr->TestExactPermissionFromPrincipal(domainWalkPrincipal, 1.573 + STS_SUBDOMAIN_PERMISSION, 1.574 + &subdomainPermission); 1.575 + NS_ENSURE_SUCCESS(rv, rv); 1.576 + *aResult = (subdomainPermission == STS_SET); 1.577 + break; 1.578 + } 1.579 + } 1.580 + // This is an ancestor, so if we get a match, we have to check if the 1.581 + // preloaded entry includes subdomains. 1.582 + else if ((preload = GetPreloadListEntry(subdomain)) != nullptr) { 1.583 + if (preload->mIncludeSubdomains) { 1.584 + SSSLOG(("%s is a preloaded STS host", subdomain)); 1.585 + *aResult = true; 1.586 + break; 1.587 + } 1.588 + } 1.589 + 1.590 + SSSLOG(("no HSTS data for %s found, walking up domain", subdomain)); 1.591 + } 1.592 + 1.593 + // Use whatever we ended up with, which defaults to false. 1.594 + return NS_OK; 1.595 +} 1.596 + 1.597 + 1.598 +// Verify the trustworthiness of the security info (are there any cert errors?) 1.599 +NS_IMETHODIMP 1.600 +nsSiteSecurityService::ShouldIgnoreHeaders(nsISupports* aSecurityInfo, 1.601 + bool* aResult) 1.602 +{ 1.603 + nsresult rv; 1.604 + bool tlsIsBroken = false; 1.605 + nsCOMPtr<nsISSLStatusProvider> sslprov = do_QueryInterface(aSecurityInfo); 1.606 + NS_ENSURE_TRUE(sslprov, NS_ERROR_FAILURE); 1.607 + 1.608 + nsCOMPtr<nsISSLStatus> sslstat; 1.609 + rv = sslprov->GetSSLStatus(getter_AddRefs(sslstat)); 1.610 + NS_ENSURE_SUCCESS(rv, rv); 1.611 + NS_ENSURE_TRUE(sslstat, NS_ERROR_FAILURE); 1.612 + 1.613 + bool trustcheck; 1.614 + rv = sslstat->GetIsDomainMismatch(&trustcheck); 1.615 + NS_ENSURE_SUCCESS(rv, rv); 1.616 + tlsIsBroken = tlsIsBroken || trustcheck; 1.617 + 1.618 + rv = sslstat->GetIsNotValidAtThisTime(&trustcheck); 1.619 + NS_ENSURE_SUCCESS(rv, rv); 1.620 + tlsIsBroken = tlsIsBroken || trustcheck; 1.621 + 1.622 + rv = sslstat->GetIsUntrusted(&trustcheck); 1.623 + NS_ENSURE_SUCCESS(rv, rv); 1.624 + tlsIsBroken = tlsIsBroken || trustcheck; 1.625 + 1.626 + *aResult = tlsIsBroken; 1.627 + return NS_OK; 1.628 +} 1.629 + 1.630 +//------------------------------------------------------------ 1.631 +// nsSiteSecurityService::nsIObserver 1.632 +//------------------------------------------------------------ 1.633 + 1.634 +NS_IMETHODIMP 1.635 +nsSiteSecurityService::Observe(nsISupports *subject, 1.636 + const char *topic, 1.637 + const char16_t *data) 1.638 +{ 1.639 + if (strcmp(topic, "last-pb-context-exited") == 0) { 1.640 + mPrivateModeHostTable.Clear(); 1.641 + } 1.642 + else if (strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) { 1.643 + mUsePreloadList = mozilla::Preferences::GetBool("network.stricttransportsecurity.preloadlist", true); 1.644 + } 1.645 + 1.646 + return NS_OK; 1.647 +} 1.648 + 1.649 +//------------------------------------------------------------ 1.650 +// Functions to overlay the permission manager calls in case 1.651 +// we're in private browsing mode. 1.652 +//------------------------------------------------------------ 1.653 +nsresult 1.654 +nsSiteSecurityService::AddPermission(nsIURI *aURI, 1.655 + const char *aType, 1.656 + uint32_t aPermission, 1.657 + uint32_t aExpireType, 1.658 + int64_t aExpireTime, 1.659 + bool aIsPrivate) 1.660 +{ 1.661 + // Private mode doesn't address user-set (EXPIRE_NEVER) permissions: let 1.662 + // those be stored persistently. 1.663 + if (!aIsPrivate || aExpireType == nsIPermissionManager::EXPIRE_NEVER) { 1.664 + // Not in private mode, or manually-set permission 1.665 + nsCOMPtr<nsIPrincipal> principal; 1.666 + nsresult rv = GetPrincipalForURI(aURI, getter_AddRefs(principal)); 1.667 + NS_ENSURE_SUCCESS(rv, rv); 1.668 + 1.669 + return mPermMgr->AddFromPrincipal(principal, aType, aPermission, 1.670 + aExpireType, aExpireTime); 1.671 + } 1.672 + 1.673 + nsAutoCString host; 1.674 + nsresult rv = GetHost(aURI, host); 1.675 + NS_ENSURE_SUCCESS(rv, rv); 1.676 + SSSLOG(("AddPermission for entry for %s", host.get())); 1.677 + 1.678 + // Update in mPrivateModeHostTable only, so any changes will be rolled 1.679 + // back when exiting private mode. 1.680 + 1.681 + // Note: EXPIRE_NEVER permissions should trump anything that shows up in 1.682 + // the HTTP header, so if there's an EXPIRE_NEVER permission already 1.683 + // don't store anything new. 1.684 + // Currently there's no way to get the type of expiry out of the 1.685 + // permission manager, but that's okay since there's nothing that stores 1.686 + // EXPIRE_NEVER permissions. 1.687 + 1.688 + // PutEntry returns an existing entry if there already is one, or it 1.689 + // creates a new one if there isn't. 1.690 + nsSSSHostEntry* entry = mPrivateModeHostTable.PutEntry(host.get()); 1.691 + if (!entry) { 1.692 + return NS_ERROR_OUT_OF_MEMORY; 1.693 + } 1.694 + SSSLOG(("Created private mode entry for %s", host.get())); 1.695 + 1.696 + // AddPermission() will be called twice if the STS header encountered has 1.697 + // includeSubdomains (first for the main permission and second for the 1.698 + // subdomains permission). If AddPermission() gets called a second time 1.699 + // with the STS_SUBDOMAIN_PERMISSION, we just have to flip that bit in 1.700 + // the nsSSSHostEntry. 1.701 + if (strcmp(aType, STS_SUBDOMAIN_PERMISSION) == 0) { 1.702 + entry->mIncludeSubdomains = true; 1.703 + } 1.704 + else if (strcmp(aType, STS_PERMISSION) == 0) { 1.705 + entry->mStsPermission = aPermission; 1.706 + } 1.707 + 1.708 + // Also refresh the expiration time. 1.709 + entry->SetExpireTime(aExpireTime); 1.710 + return NS_OK; 1.711 +} 1.712 + 1.713 +nsresult 1.714 +nsSiteSecurityService::RemovePermission(const nsCString &aHost, 1.715 + const char *aType, 1.716 + bool aIsPrivate) 1.717 +{ 1.718 + // Build up a principal for use with the permission manager. 1.719 + // normalize all URIs with https:// 1.720 + nsCOMPtr<nsIURI> uri; 1.721 + nsresult rv = NS_NewURI(getter_AddRefs(uri), 1.722 + NS_LITERAL_CSTRING("https://") + aHost); 1.723 + NS_ENSURE_SUCCESS(rv, rv); 1.724 + 1.725 + nsCOMPtr<nsIPrincipal> principal; 1.726 + rv = GetPrincipalForURI(uri, getter_AddRefs(principal)); 1.727 + NS_ENSURE_SUCCESS(rv, rv); 1.728 + 1.729 + if (!aIsPrivate) { 1.730 + // Not in private mode: remove permissions persistently. 1.731 + // This means setting the permission to STS_KNOCKOUT in case 1.732 + // this host is on the preload list (so we can override it). 1.733 + return mPermMgr->AddFromPrincipal(principal, aType, 1.734 + STS_KNOCKOUT, 1.735 + nsIPermissionManager::EXPIRE_NEVER, 0); 1.736 + } 1.737 + 1.738 + // Make changes in mPrivateModeHostTable only, so any changes will be 1.739 + // rolled back when exiting private mode. 1.740 + nsSSSHostEntry* entry = mPrivateModeHostTable.GetEntry(aHost.get()); 1.741 + 1.742 + if (!entry) { 1.743 + entry = mPrivateModeHostTable.PutEntry(aHost.get()); 1.744 + if (!entry) { 1.745 + return NS_ERROR_OUT_OF_MEMORY; 1.746 + } 1.747 + SSSLOG(("Created private mode deleted mask for %s", aHost.get())); 1.748 + } 1.749 + 1.750 + if (strcmp(aType, STS_PERMISSION) == 0) { 1.751 + entry->mStsPermission = STS_KNOCKOUT; 1.752 + } 1.753 + else if (strcmp(aType, STS_SUBDOMAIN_PERMISSION) == 0) { 1.754 + entry->mIncludeSubdomains = false; 1.755 + } 1.756 + 1.757 + return NS_OK; 1.758 +}