michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "plstr.h" michael@0: #include "prlog.h" michael@0: #include "prprf.h" michael@0: #include "prnetdb.h" michael@0: #include "nsCRTGlue.h" michael@0: #include "nsIPermissionManager.h" michael@0: #include "nsISSLStatus.h" michael@0: #include "nsISSLStatusProvider.h" michael@0: #include "nsSiteSecurityService.h" michael@0: #include "nsIURI.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsString.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsISocketProvider.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/LinkedList.h" michael@0: #include "nsSecurityHeaderParser.h" michael@0: michael@0: // A note about the preload list: michael@0: // When a site specifically disables sts by sending a header with michael@0: // 'max-age: 0', we keep a "knockout" value that means "we have no information michael@0: // regarding the sts state of this host" (any ancestor of "this host" can still michael@0: // influence its sts status via include subdomains, however). michael@0: // This prevents the preload list from overriding the site's current michael@0: // desired sts status. Knockout values are indicated by permission values of michael@0: // STS_KNOCKOUT. michael@0: #include "nsSTSPreloadList.inc" michael@0: michael@0: #define STS_SET (nsIPermissionManager::ALLOW_ACTION) michael@0: #define STS_UNSET (nsIPermissionManager::UNKNOWN_ACTION) michael@0: #define STS_KNOCKOUT (nsIPermissionManager::DENY_ACTION) michael@0: michael@0: #if defined(PR_LOGGING) michael@0: static PRLogModuleInfo * michael@0: GetSSSLog() michael@0: { michael@0: static PRLogModuleInfo *gSSSLog; michael@0: if (!gSSSLog) michael@0: gSSSLog = PR_NewLogModule("nsSSService"); michael@0: return gSSSLog; michael@0: } michael@0: #endif michael@0: michael@0: #define SSSLOG(args) PR_LOG(GetSSSLog(), 4, args) michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: nsSSSHostEntry::nsSSSHostEntry(const char* aHost) michael@0: : mHost(aHost) michael@0: , mExpireTime(0) michael@0: , mStsPermission(STS_UNSET) michael@0: , mExpired(false) michael@0: , mIncludeSubdomains(false) michael@0: { michael@0: } michael@0: michael@0: nsSSSHostEntry::nsSSSHostEntry(const nsSSSHostEntry& toCopy) michael@0: : mHost(toCopy.mHost) michael@0: , mExpireTime(toCopy.mExpireTime) michael@0: , mStsPermission(toCopy.mStsPermission) michael@0: , mExpired(toCopy.mExpired) michael@0: , mIncludeSubdomains(toCopy.mIncludeSubdomains) michael@0: { michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: michael@0: nsSiteSecurityService::nsSiteSecurityService() michael@0: : mUsePreloadList(true) michael@0: { michael@0: } michael@0: michael@0: nsSiteSecurityService::~nsSiteSecurityService() michael@0: { michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsSiteSecurityService, michael@0: nsIObserver, michael@0: nsISiteSecurityService) michael@0: michael@0: nsresult michael@0: nsSiteSecurityService::Init() michael@0: { michael@0: nsresult rv; michael@0: michael@0: mPermMgr = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mUsePreloadList = mozilla::Preferences::GetBool("network.stricttransportsecurity.preloadlist", true); michael@0: mozilla::Preferences::AddStrongObserver(this, "network.stricttransportsecurity.preloadlist"); michael@0: mObserverService = mozilla::services::GetObserverService(); michael@0: if (mObserverService) michael@0: mObserverService->AddObserver(this, "last-pb-context-exited", false); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsSiteSecurityService::GetHost(nsIURI *aURI, nsACString &aResult) michael@0: { michael@0: nsCOMPtr innerURI = NS_GetInnermostURI(aURI); michael@0: if (!innerURI) return NS_ERROR_FAILURE; michael@0: michael@0: nsresult rv = innerURI->GetAsciiHost(aResult); michael@0: michael@0: if (NS_FAILED(rv) || aResult.IsEmpty()) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsSiteSecurityService::GetPrincipalForURI(nsIURI* aURI, michael@0: nsIPrincipal** aPrincipal) michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr securityManager = michael@0: do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // We have to normalize the scheme of the URIs we're using, so just use https. michael@0: // HSTS information is shared across all ports for a given host. michael@0: nsAutoCString host; michael@0: rv = GetHost(aURI, host); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: nsCOMPtr uri; michael@0: rv = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://") + host); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // We want all apps to share HSTS state, so this is one of the few places michael@0: // where we do not silo persistent state by extended origin. michael@0: return securityManager->GetNoAppCodebasePrincipal(uri, aPrincipal); michael@0: } michael@0: michael@0: nsresult michael@0: nsSiteSecurityService::SetState(uint32_t aType, michael@0: nsIURI* aSourceURI, michael@0: int64_t maxage, michael@0: bool includeSubdomains, michael@0: uint32_t flags) michael@0: { michael@0: // If max-age is zero, that's an indication to immediately remove the michael@0: // permissions, so here's a shortcut. michael@0: if (!maxage) { michael@0: return RemoveState(aType, aSourceURI, flags); michael@0: } michael@0: michael@0: // Expire time is millis from now. Since STS max-age is in seconds, and michael@0: // PR_Now() is in micros, must equalize the units at milliseconds. michael@0: int64_t expiretime = (PR_Now() / PR_USEC_PER_MSEC) + michael@0: (maxage * PR_MSEC_PER_SEC); michael@0: michael@0: bool isPrivate = flags & nsISocketProvider::NO_PERMANENT_STORAGE; michael@0: michael@0: // record entry for this host with max-age in the permissions manager michael@0: SSSLOG(("SSS: maxage permission SET, adding permission\n")); michael@0: nsresult rv = AddPermission(aSourceURI, michael@0: STS_PERMISSION, michael@0: (uint32_t) STS_SET, michael@0: (uint32_t) nsIPermissionManager::EXPIRE_TIME, michael@0: expiretime, michael@0: isPrivate); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (includeSubdomains) { michael@0: // record entry for this host with include subdomains in the permissions manager michael@0: SSSLOG(("SSS: subdomains permission SET, adding permission\n")); michael@0: rv = AddPermission(aSourceURI, michael@0: STS_SUBDOMAIN_PERMISSION, michael@0: (uint32_t) STS_SET, michael@0: (uint32_t) nsIPermissionManager::EXPIRE_TIME, michael@0: expiretime, michael@0: isPrivate); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } else { // !includeSubdomains michael@0: nsAutoCString hostname; michael@0: rv = GetHost(aSourceURI, hostname); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: SSSLOG(("SSS: subdomains permission UNSET, removing any existing ones\n")); michael@0: rv = RemovePermission(hostname, STS_SUBDOMAIN_PERMISSION, isPrivate); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSiteSecurityService::RemoveState(uint32_t aType, nsIURI* aURI, uint32_t aFlags) michael@0: { michael@0: // Should be called on the main thread (or via proxy) since the permission michael@0: // manager is used and it's not threadsafe. michael@0: NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED); michael@0: // Only HSTS is supported at the moment. michael@0: NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS, michael@0: NS_ERROR_NOT_IMPLEMENTED); michael@0: michael@0: nsAutoCString hostname; michael@0: nsresult rv = GetHost(aURI, hostname); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool isPrivate = aFlags & nsISocketProvider::NO_PERMANENT_STORAGE; michael@0: michael@0: rv = RemovePermission(hostname, STS_PERMISSION, isPrivate); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: SSSLOG(("SSS: deleted maxage permission\n")); michael@0: michael@0: rv = RemovePermission(hostname, STS_SUBDOMAIN_PERMISSION, isPrivate); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: SSSLOG(("SSS: deleted subdomains permission\n")); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static bool michael@0: HostIsIPAddress(const char *hostname) michael@0: { michael@0: PRNetAddr hostAddr; michael@0: return (PR_StringToNetAddr(hostname, &hostAddr) == PR_SUCCESS); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSiteSecurityService::ProcessHeader(uint32_t aType, michael@0: nsIURI* aSourceURI, michael@0: const char* aHeader, michael@0: uint32_t aFlags, michael@0: uint64_t *aMaxAge, michael@0: bool *aIncludeSubdomains) michael@0: { michael@0: // Should be called on the main thread (or via proxy) since the permission michael@0: // manager is used and it's not threadsafe. michael@0: NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED); michael@0: // Only HSTS is supported at the moment. michael@0: NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS, michael@0: NS_ERROR_NOT_IMPLEMENTED); michael@0: michael@0: if (aMaxAge != nullptr) { michael@0: *aMaxAge = 0; michael@0: } michael@0: michael@0: if (aIncludeSubdomains != nullptr) { michael@0: *aIncludeSubdomains = false; michael@0: } michael@0: michael@0: nsAutoCString host; michael@0: nsresult rv = GetHost(aSourceURI, host); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (HostIsIPAddress(host.get())) { michael@0: /* Don't process headers if a site is accessed by IP address. */ michael@0: return NS_OK; michael@0: } michael@0: michael@0: char * header = NS_strdup(aHeader); michael@0: if (!header) return NS_ERROR_OUT_OF_MEMORY; michael@0: rv = ProcessHeaderMutating(aType, aSourceURI, header, aFlags, michael@0: aMaxAge, aIncludeSubdomains); michael@0: NS_Free(header); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsSiteSecurityService::ProcessHeaderMutating(uint32_t aType, michael@0: nsIURI* aSourceURI, michael@0: char* aHeader, michael@0: uint32_t aFlags, michael@0: uint64_t *aMaxAge, michael@0: bool *aIncludeSubdomains) michael@0: { michael@0: SSSLOG(("SSS: processing header '%s'", aHeader)); michael@0: michael@0: // "Strict-Transport-Security" ":" OWS michael@0: // STS-d *( OWS ";" OWS STS-d OWS) michael@0: // michael@0: // ; STS directive michael@0: // STS-d = maxAge / includeSubDomains michael@0: // michael@0: // maxAge = "max-age" "=" delta-seconds v-ext michael@0: // michael@0: // includeSubDomains = [ "includeSubDomains" ] michael@0: // michael@0: // The order of the directives is not significant. michael@0: // All directives must appear only once. michael@0: // Directive names are case-insensitive. michael@0: // The entire header is invalid if a directive not conforming to the michael@0: // syntax is encountered. michael@0: // Unrecognized directives (that are otherwise syntactically valid) are michael@0: // ignored, and the rest of the header is parsed as normal. michael@0: michael@0: bool foundMaxAge = false; michael@0: bool foundIncludeSubdomains = false; michael@0: bool foundUnrecognizedDirective = false; michael@0: int64_t maxAge = 0; michael@0: michael@0: NS_NAMED_LITERAL_CSTRING(max_age_var, "max-age"); michael@0: NS_NAMED_LITERAL_CSTRING(include_subd_var, "includesubdomains"); michael@0: michael@0: michael@0: nsSecurityHeaderParser parser(aHeader); michael@0: nsresult rv = parser.Parse(); michael@0: if (NS_FAILED(rv)) { michael@0: SSSLOG(("SSS: could not parse header")); michael@0: return rv; michael@0: } michael@0: mozilla::LinkedList *directives = parser.GetDirectives(); michael@0: michael@0: for (nsSecurityHeaderDirective *directive = directives->getFirst(); michael@0: directive != nullptr; directive = directive->getNext()) { michael@0: if (directive->mName.Length() == max_age_var.Length() && michael@0: directive->mName.EqualsIgnoreCase(max_age_var.get(), michael@0: max_age_var.Length())) { michael@0: if (foundMaxAge) { michael@0: SSSLOG(("SSS: found two max-age directives")); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: SSSLOG(("SSS: found max-age directive")); michael@0: foundMaxAge = true; michael@0: michael@0: size_t len = directive->mValue.Length(); michael@0: for (size_t i = 0; i < len; i++) { michael@0: char chr = directive->mValue.CharAt(i); michael@0: if (chr < '0' || chr > '9') { michael@0: SSSLOG(("SSS: invalid value for max-age directive")); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: if (PR_sscanf(directive->mValue.get(), "%lld", &maxAge) != 1) { michael@0: SSSLOG(("SSS: could not parse delta-seconds")); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: SSSLOG(("SSS: parsed delta-seconds: %lld", maxAge)); michael@0: } else if (directive->mName.Length() == include_subd_var.Length() && michael@0: directive->mName.EqualsIgnoreCase(include_subd_var.get(), michael@0: include_subd_var.Length())) { michael@0: if (foundIncludeSubdomains) { michael@0: SSSLOG(("SSS: found two includeSubdomains directives")); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: SSSLOG(("SSS: found includeSubdomains directive")); michael@0: foundIncludeSubdomains = true; michael@0: michael@0: if (directive->mValue.Length() != 0) { michael@0: SSSLOG(("SSS: includeSubdomains directive unexpectedly had value '%s'", directive->mValue.get())); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } else { michael@0: SSSLOG(("SSS: ignoring unrecognized directive '%s'", directive->mName.get())); michael@0: foundUnrecognizedDirective = true; michael@0: } michael@0: } michael@0: michael@0: // after processing all the directives, make sure we came across max-age michael@0: // somewhere. michael@0: if (!foundMaxAge) { michael@0: SSSLOG(("SSS: did not encounter required max-age directive")); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // record the successfully parsed header data. michael@0: SetState(aType, aSourceURI, maxAge, foundIncludeSubdomains, aFlags); michael@0: michael@0: if (aMaxAge != nullptr) { michael@0: *aMaxAge = (uint64_t)maxAge; michael@0: } michael@0: michael@0: if (aIncludeSubdomains != nullptr) { michael@0: *aIncludeSubdomains = foundIncludeSubdomains; michael@0: } michael@0: michael@0: return foundUnrecognizedDirective ? michael@0: NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA : michael@0: NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSiteSecurityService::IsSecureHost(uint32_t aType, const char* aHost, michael@0: uint32_t aFlags, bool* aResult) michael@0: { michael@0: // Should be called on the main thread (or via proxy) since the permission michael@0: // manager is used and it's not threadsafe. michael@0: NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED); michael@0: // Only HSTS is supported at the moment. michael@0: NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS, michael@0: NS_ERROR_NOT_IMPLEMENTED); michael@0: michael@0: /* An IP address never qualifies as a secure URI. */ michael@0: if (HostIsIPAddress(aHost)) { michael@0: *aResult = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr uri; michael@0: nsDependentCString hostString(aHost); michael@0: nsresult rv = NS_NewURI(getter_AddRefs(uri), michael@0: NS_LITERAL_CSTRING("https://") + hostString); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: return IsSecureURI(aType, uri, aFlags, aResult); michael@0: } michael@0: michael@0: int STSPreloadCompare(const void *key, const void *entry) michael@0: { michael@0: const char *keyStr = (const char *)key; michael@0: const nsSTSPreload *preloadEntry = (const nsSTSPreload *)entry; michael@0: return strcmp(keyStr, preloadEntry->mHost); michael@0: } michael@0: michael@0: // Returns the preload list entry for the given host, if it exists. michael@0: // Only does exact host matching - the user must decide how to use the returned michael@0: // data. May return null. michael@0: const nsSTSPreload * michael@0: nsSiteSecurityService::GetPreloadListEntry(const char *aHost) michael@0: { michael@0: PRTime currentTime = PR_Now(); michael@0: int32_t timeOffset = 0; michael@0: nsresult rv = mozilla::Preferences::GetInt("test.currentTimeOffsetSeconds", michael@0: &timeOffset); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: currentTime += (PRTime(timeOffset) * PR_USEC_PER_SEC); michael@0: } michael@0: michael@0: if (mUsePreloadList && currentTime < gPreloadListExpirationTime) { michael@0: return (const nsSTSPreload *) bsearch(aHost, michael@0: kSTSPreloadList, michael@0: mozilla::ArrayLength(kSTSPreloadList), michael@0: sizeof(nsSTSPreload), michael@0: STSPreloadCompare); michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSiteSecurityService::IsSecureURI(uint32_t aType, nsIURI* aURI, michael@0: uint32_t aFlags, bool* aResult) michael@0: { michael@0: // Should be called on the main thread (or via proxy) since the permission michael@0: // manager is used and it's not threadsafe. michael@0: NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED); michael@0: // Only HSTS is supported at the moment. michael@0: NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS, michael@0: NS_ERROR_NOT_IMPLEMENTED); michael@0: michael@0: // set default in case if we can't find any STS information michael@0: *aResult = false; michael@0: michael@0: nsAutoCString host; michael@0: nsresult rv = GetHost(aURI, host); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: /* An IP address never qualifies as a secure URI. */ michael@0: if (HostIsIPAddress(host.BeginReading())) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Holepunch chart.apis.google.com and subdomains. michael@0: if (host.Equals(NS_LITERAL_CSTRING("chart.apis.google.com")) || michael@0: StringEndsWith(host, NS_LITERAL_CSTRING(".chart.apis.google.com"))) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: const nsSTSPreload *preload = nullptr; michael@0: nsSSSHostEntry *pbEntry = nullptr; michael@0: michael@0: bool isPrivate = aFlags & nsISocketProvider::NO_PERMANENT_STORAGE; michael@0: if (isPrivate) { michael@0: pbEntry = mPrivateModeHostTable.GetEntry(host.get()); michael@0: } michael@0: michael@0: nsCOMPtr principal; michael@0: rv = GetPrincipalForURI(aURI, getter_AddRefs(principal)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t permMgrPermission; michael@0: rv = mPermMgr->TestExactPermissionFromPrincipal(principal, STS_PERMISSION, michael@0: &permMgrPermission); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // First check the exact host. This involves first checking for an entry in michael@0: // the private browsing table. If that entry exists, we don't want to check michael@0: // in either the permission manager or the preload list. We only want to use michael@0: // the stored permission if it is not a knockout entry, however. michael@0: // Additionally, if it is a knockout entry, we want to stop looking for data michael@0: // on the host, because the knockout entry indicates "we have no information michael@0: // regarding the sts status of this host". michael@0: if (pbEntry && pbEntry->mStsPermission != STS_UNSET) { michael@0: SSSLOG(("Found private browsing table entry for %s", host.get())); michael@0: if (!pbEntry->IsExpired() && pbEntry->mStsPermission == STS_SET) { michael@0: *aResult = true; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: // Next we look in the permission manager. Same story here regarding michael@0: // knockout entries. michael@0: else if (permMgrPermission != STS_UNSET) { michael@0: SSSLOG(("Found permission manager entry for %s", host.get())); michael@0: if (permMgrPermission == STS_SET) { michael@0: *aResult = true; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: // Finally look in the preloaded list. This is the exact host, michael@0: // so if an entry exists at all, this host is sts. michael@0: else if (GetPreloadListEntry(host.get())) { michael@0: SSSLOG(("%s is a preloaded STS host", host.get())); michael@0: *aResult = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Used for testing permissions as we walk up the domain tree. michael@0: nsCOMPtr domainWalkURI; michael@0: nsCOMPtr domainWalkPrincipal; michael@0: const char *subdomain; michael@0: michael@0: SSSLOG(("no HSTS data for %s found, walking up domain", host.get())); michael@0: uint32_t offset = 0; michael@0: for (offset = host.FindChar('.', offset) + 1; michael@0: offset > 0; michael@0: offset = host.FindChar('.', offset) + 1) { michael@0: michael@0: subdomain = host.get() + offset; michael@0: michael@0: // If we get an empty string, don't continue. michael@0: if (strlen(subdomain) < 1) { michael@0: break; michael@0: } michael@0: michael@0: if (isPrivate) { michael@0: pbEntry = mPrivateModeHostTable.GetEntry(subdomain); michael@0: } michael@0: michael@0: // normalize all URIs with https:// michael@0: rv = NS_NewURI(getter_AddRefs(domainWalkURI), michael@0: NS_LITERAL_CSTRING("https://") + Substring(host, offset)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = GetPrincipalForURI(domainWalkURI, getter_AddRefs(domainWalkPrincipal)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mPermMgr->TestExactPermissionFromPrincipal(domainWalkPrincipal, michael@0: STS_PERMISSION, michael@0: &permMgrPermission); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Do the same thing as with the exact host, except now we're looking at michael@0: // ancestor domains of the original host. So, we have to look at the michael@0: // include subdomains permissions (although we still have to check for the michael@0: // STS_PERMISSION first to check that this is an sts host and not a michael@0: // knockout entry - and again, if it is a knockout entry, we stop looking michael@0: // for data on it and skip to the next higher up ancestor domain). michael@0: if (pbEntry && pbEntry->mStsPermission != STS_UNSET) { michael@0: SSSLOG(("Found private browsing table entry for %s", subdomain)); michael@0: if (!pbEntry->IsExpired() && pbEntry->mStsPermission == STS_SET) { michael@0: *aResult = pbEntry->mIncludeSubdomains; michael@0: break; michael@0: } michael@0: } michael@0: else if (permMgrPermission != STS_UNSET) { michael@0: SSSLOG(("Found permission manager entry for %s", subdomain)); michael@0: if (permMgrPermission == STS_SET) { michael@0: uint32_t subdomainPermission; michael@0: rv = mPermMgr->TestExactPermissionFromPrincipal(domainWalkPrincipal, michael@0: STS_SUBDOMAIN_PERMISSION, michael@0: &subdomainPermission); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: *aResult = (subdomainPermission == STS_SET); michael@0: break; michael@0: } michael@0: } michael@0: // This is an ancestor, so if we get a match, we have to check if the michael@0: // preloaded entry includes subdomains. michael@0: else if ((preload = GetPreloadListEntry(subdomain)) != nullptr) { michael@0: if (preload->mIncludeSubdomains) { michael@0: SSSLOG(("%s is a preloaded STS host", subdomain)); michael@0: *aResult = true; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: SSSLOG(("no HSTS data for %s found, walking up domain", subdomain)); michael@0: } michael@0: michael@0: // Use whatever we ended up with, which defaults to false. michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // Verify the trustworthiness of the security info (are there any cert errors?) michael@0: NS_IMETHODIMP michael@0: nsSiteSecurityService::ShouldIgnoreHeaders(nsISupports* aSecurityInfo, michael@0: bool* aResult) michael@0: { michael@0: nsresult rv; michael@0: bool tlsIsBroken = false; michael@0: nsCOMPtr sslprov = do_QueryInterface(aSecurityInfo); michael@0: NS_ENSURE_TRUE(sslprov, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr sslstat; michael@0: rv = sslprov->GetSSLStatus(getter_AddRefs(sslstat)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(sslstat, NS_ERROR_FAILURE); michael@0: michael@0: bool trustcheck; michael@0: rv = sslstat->GetIsDomainMismatch(&trustcheck); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: tlsIsBroken = tlsIsBroken || trustcheck; michael@0: michael@0: rv = sslstat->GetIsNotValidAtThisTime(&trustcheck); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: tlsIsBroken = tlsIsBroken || trustcheck; michael@0: michael@0: rv = sslstat->GetIsUntrusted(&trustcheck); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: tlsIsBroken = tlsIsBroken || trustcheck; michael@0: michael@0: *aResult = tlsIsBroken; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //------------------------------------------------------------ michael@0: // nsSiteSecurityService::nsIObserver michael@0: //------------------------------------------------------------ michael@0: michael@0: NS_IMETHODIMP michael@0: nsSiteSecurityService::Observe(nsISupports *subject, michael@0: const char *topic, michael@0: const char16_t *data) michael@0: { michael@0: if (strcmp(topic, "last-pb-context-exited") == 0) { michael@0: mPrivateModeHostTable.Clear(); michael@0: } michael@0: else if (strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) { michael@0: mUsePreloadList = mozilla::Preferences::GetBool("network.stricttransportsecurity.preloadlist", true); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //------------------------------------------------------------ michael@0: // Functions to overlay the permission manager calls in case michael@0: // we're in private browsing mode. michael@0: //------------------------------------------------------------ michael@0: nsresult michael@0: nsSiteSecurityService::AddPermission(nsIURI *aURI, michael@0: const char *aType, michael@0: uint32_t aPermission, michael@0: uint32_t aExpireType, michael@0: int64_t aExpireTime, michael@0: bool aIsPrivate) michael@0: { michael@0: // Private mode doesn't address user-set (EXPIRE_NEVER) permissions: let michael@0: // those be stored persistently. michael@0: if (!aIsPrivate || aExpireType == nsIPermissionManager::EXPIRE_NEVER) { michael@0: // Not in private mode, or manually-set permission michael@0: nsCOMPtr principal; michael@0: nsresult rv = GetPrincipalForURI(aURI, getter_AddRefs(principal)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return mPermMgr->AddFromPrincipal(principal, aType, aPermission, michael@0: aExpireType, aExpireTime); michael@0: } michael@0: michael@0: nsAutoCString host; michael@0: nsresult rv = GetHost(aURI, host); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: SSSLOG(("AddPermission for entry for %s", host.get())); michael@0: michael@0: // Update in mPrivateModeHostTable only, so any changes will be rolled michael@0: // back when exiting private mode. michael@0: michael@0: // Note: EXPIRE_NEVER permissions should trump anything that shows up in michael@0: // the HTTP header, so if there's an EXPIRE_NEVER permission already michael@0: // don't store anything new. michael@0: // Currently there's no way to get the type of expiry out of the michael@0: // permission manager, but that's okay since there's nothing that stores michael@0: // EXPIRE_NEVER permissions. michael@0: michael@0: // PutEntry returns an existing entry if there already is one, or it michael@0: // creates a new one if there isn't. michael@0: nsSSSHostEntry* entry = mPrivateModeHostTable.PutEntry(host.get()); michael@0: if (!entry) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: SSSLOG(("Created private mode entry for %s", host.get())); michael@0: michael@0: // AddPermission() will be called twice if the STS header encountered has michael@0: // includeSubdomains (first for the main permission and second for the michael@0: // subdomains permission). If AddPermission() gets called a second time michael@0: // with the STS_SUBDOMAIN_PERMISSION, we just have to flip that bit in michael@0: // the nsSSSHostEntry. michael@0: if (strcmp(aType, STS_SUBDOMAIN_PERMISSION) == 0) { michael@0: entry->mIncludeSubdomains = true; michael@0: } michael@0: else if (strcmp(aType, STS_PERMISSION) == 0) { michael@0: entry->mStsPermission = aPermission; michael@0: } michael@0: michael@0: // Also refresh the expiration time. michael@0: entry->SetExpireTime(aExpireTime); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsSiteSecurityService::RemovePermission(const nsCString &aHost, michael@0: const char *aType, michael@0: bool aIsPrivate) michael@0: { michael@0: // Build up a principal for use with the permission manager. michael@0: // normalize all URIs with https:// michael@0: nsCOMPtr uri; michael@0: nsresult rv = NS_NewURI(getter_AddRefs(uri), michael@0: NS_LITERAL_CSTRING("https://") + aHost); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr principal; michael@0: rv = GetPrincipalForURI(uri, getter_AddRefs(principal)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!aIsPrivate) { michael@0: // Not in private mode: remove permissions persistently. michael@0: // This means setting the permission to STS_KNOCKOUT in case michael@0: // this host is on the preload list (so we can override it). michael@0: return mPermMgr->AddFromPrincipal(principal, aType, michael@0: STS_KNOCKOUT, michael@0: nsIPermissionManager::EXPIRE_NEVER, 0); michael@0: } michael@0: michael@0: // Make changes in mPrivateModeHostTable only, so any changes will be michael@0: // rolled back when exiting private mode. michael@0: nsSSSHostEntry* entry = mPrivateModeHostTable.GetEntry(aHost.get()); michael@0: michael@0: if (!entry) { michael@0: entry = mPrivateModeHostTable.PutEntry(aHost.get()); michael@0: if (!entry) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: SSSLOG(("Created private mode deleted mask for %s", aHost.get())); michael@0: } michael@0: michael@0: if (strcmp(aType, STS_PERMISSION) == 0) { michael@0: entry->mStsPermission = STS_KNOCKOUT; michael@0: } michael@0: else if (strcmp(aType, STS_SUBDOMAIN_PERMISSION) == 0) { michael@0: entry->mIncludeSubdomains = false; michael@0: } michael@0: michael@0: return NS_OK; michael@0: }