security/manager/boot/src/nsSiteSecurityService.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 #include "plstr.h"
michael@0 6 #include "prlog.h"
michael@0 7 #include "prprf.h"
michael@0 8 #include "prnetdb.h"
michael@0 9 #include "nsCRTGlue.h"
michael@0 10 #include "nsIPermissionManager.h"
michael@0 11 #include "nsISSLStatus.h"
michael@0 12 #include "nsISSLStatusProvider.h"
michael@0 13 #include "nsSiteSecurityService.h"
michael@0 14 #include "nsIURI.h"
michael@0 15 #include "nsNetUtil.h"
michael@0 16 #include "nsThreadUtils.h"
michael@0 17 #include "nsString.h"
michael@0 18 #include "nsIScriptSecurityManager.h"
michael@0 19 #include "nsISocketProvider.h"
michael@0 20 #include "mozilla/Preferences.h"
michael@0 21 #include "mozilla/LinkedList.h"
michael@0 22 #include "nsSecurityHeaderParser.h"
michael@0 23
michael@0 24 // A note about the preload list:
michael@0 25 // When a site specifically disables sts by sending a header with
michael@0 26 // 'max-age: 0', we keep a "knockout" value that means "we have no information
michael@0 27 // regarding the sts state of this host" (any ancestor of "this host" can still
michael@0 28 // influence its sts status via include subdomains, however).
michael@0 29 // This prevents the preload list from overriding the site's current
michael@0 30 // desired sts status. Knockout values are indicated by permission values of
michael@0 31 // STS_KNOCKOUT.
michael@0 32 #include "nsSTSPreloadList.inc"
michael@0 33
michael@0 34 #define STS_SET (nsIPermissionManager::ALLOW_ACTION)
michael@0 35 #define STS_UNSET (nsIPermissionManager::UNKNOWN_ACTION)
michael@0 36 #define STS_KNOCKOUT (nsIPermissionManager::DENY_ACTION)
michael@0 37
michael@0 38 #if defined(PR_LOGGING)
michael@0 39 static PRLogModuleInfo *
michael@0 40 GetSSSLog()
michael@0 41 {
michael@0 42 static PRLogModuleInfo *gSSSLog;
michael@0 43 if (!gSSSLog)
michael@0 44 gSSSLog = PR_NewLogModule("nsSSService");
michael@0 45 return gSSSLog;
michael@0 46 }
michael@0 47 #endif
michael@0 48
michael@0 49 #define SSSLOG(args) PR_LOG(GetSSSLog(), 4, args)
michael@0 50
michael@0 51 ////////////////////////////////////////////////////////////////////////////////
michael@0 52
michael@0 53 nsSSSHostEntry::nsSSSHostEntry(const char* aHost)
michael@0 54 : mHost(aHost)
michael@0 55 , mExpireTime(0)
michael@0 56 , mStsPermission(STS_UNSET)
michael@0 57 , mExpired(false)
michael@0 58 , mIncludeSubdomains(false)
michael@0 59 {
michael@0 60 }
michael@0 61
michael@0 62 nsSSSHostEntry::nsSSSHostEntry(const nsSSSHostEntry& toCopy)
michael@0 63 : mHost(toCopy.mHost)
michael@0 64 , mExpireTime(toCopy.mExpireTime)
michael@0 65 , mStsPermission(toCopy.mStsPermission)
michael@0 66 , mExpired(toCopy.mExpired)
michael@0 67 , mIncludeSubdomains(toCopy.mIncludeSubdomains)
michael@0 68 {
michael@0 69 }
michael@0 70
michael@0 71 ////////////////////////////////////////////////////////////////////////////////
michael@0 72
michael@0 73
michael@0 74 nsSiteSecurityService::nsSiteSecurityService()
michael@0 75 : mUsePreloadList(true)
michael@0 76 {
michael@0 77 }
michael@0 78
michael@0 79 nsSiteSecurityService::~nsSiteSecurityService()
michael@0 80 {
michael@0 81 }
michael@0 82
michael@0 83 NS_IMPL_ISUPPORTS(nsSiteSecurityService,
michael@0 84 nsIObserver,
michael@0 85 nsISiteSecurityService)
michael@0 86
michael@0 87 nsresult
michael@0 88 nsSiteSecurityService::Init()
michael@0 89 {
michael@0 90 nsresult rv;
michael@0 91
michael@0 92 mPermMgr = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
michael@0 93 NS_ENSURE_SUCCESS(rv, rv);
michael@0 94
michael@0 95 mUsePreloadList = mozilla::Preferences::GetBool("network.stricttransportsecurity.preloadlist", true);
michael@0 96 mozilla::Preferences::AddStrongObserver(this, "network.stricttransportsecurity.preloadlist");
michael@0 97 mObserverService = mozilla::services::GetObserverService();
michael@0 98 if (mObserverService)
michael@0 99 mObserverService->AddObserver(this, "last-pb-context-exited", false);
michael@0 100
michael@0 101 return NS_OK;
michael@0 102 }
michael@0 103
michael@0 104 nsresult
michael@0 105 nsSiteSecurityService::GetHost(nsIURI *aURI, nsACString &aResult)
michael@0 106 {
michael@0 107 nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
michael@0 108 if (!innerURI) return NS_ERROR_FAILURE;
michael@0 109
michael@0 110 nsresult rv = innerURI->GetAsciiHost(aResult);
michael@0 111
michael@0 112 if (NS_FAILED(rv) || aResult.IsEmpty())
michael@0 113 return NS_ERROR_UNEXPECTED;
michael@0 114
michael@0 115 return NS_OK;
michael@0 116 }
michael@0 117
michael@0 118 nsresult
michael@0 119 nsSiteSecurityService::GetPrincipalForURI(nsIURI* aURI,
michael@0 120 nsIPrincipal** aPrincipal)
michael@0 121 {
michael@0 122 nsresult rv;
michael@0 123 nsCOMPtr<nsIScriptSecurityManager> securityManager =
michael@0 124 do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
michael@0 125 NS_ENSURE_SUCCESS(rv, rv);
michael@0 126
michael@0 127 // We have to normalize the scheme of the URIs we're using, so just use https.
michael@0 128 // HSTS information is shared across all ports for a given host.
michael@0 129 nsAutoCString host;
michael@0 130 rv = GetHost(aURI, host);
michael@0 131 NS_ENSURE_SUCCESS(rv, rv);
michael@0 132 nsCOMPtr<nsIURI> uri;
michael@0 133 rv = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://") + host);
michael@0 134 NS_ENSURE_SUCCESS(rv, rv);
michael@0 135
michael@0 136 // We want all apps to share HSTS state, so this is one of the few places
michael@0 137 // where we do not silo persistent state by extended origin.
michael@0 138 return securityManager->GetNoAppCodebasePrincipal(uri, aPrincipal);
michael@0 139 }
michael@0 140
michael@0 141 nsresult
michael@0 142 nsSiteSecurityService::SetState(uint32_t aType,
michael@0 143 nsIURI* aSourceURI,
michael@0 144 int64_t maxage,
michael@0 145 bool includeSubdomains,
michael@0 146 uint32_t flags)
michael@0 147 {
michael@0 148 // If max-age is zero, that's an indication to immediately remove the
michael@0 149 // permissions, so here's a shortcut.
michael@0 150 if (!maxage) {
michael@0 151 return RemoveState(aType, aSourceURI, flags);
michael@0 152 }
michael@0 153
michael@0 154 // Expire time is millis from now. Since STS max-age is in seconds, and
michael@0 155 // PR_Now() is in micros, must equalize the units at milliseconds.
michael@0 156 int64_t expiretime = (PR_Now() / PR_USEC_PER_MSEC) +
michael@0 157 (maxage * PR_MSEC_PER_SEC);
michael@0 158
michael@0 159 bool isPrivate = flags & nsISocketProvider::NO_PERMANENT_STORAGE;
michael@0 160
michael@0 161 // record entry for this host with max-age in the permissions manager
michael@0 162 SSSLOG(("SSS: maxage permission SET, adding permission\n"));
michael@0 163 nsresult rv = AddPermission(aSourceURI,
michael@0 164 STS_PERMISSION,
michael@0 165 (uint32_t) STS_SET,
michael@0 166 (uint32_t) nsIPermissionManager::EXPIRE_TIME,
michael@0 167 expiretime,
michael@0 168 isPrivate);
michael@0 169 NS_ENSURE_SUCCESS(rv, rv);
michael@0 170
michael@0 171 if (includeSubdomains) {
michael@0 172 // record entry for this host with include subdomains in the permissions manager
michael@0 173 SSSLOG(("SSS: subdomains permission SET, adding permission\n"));
michael@0 174 rv = AddPermission(aSourceURI,
michael@0 175 STS_SUBDOMAIN_PERMISSION,
michael@0 176 (uint32_t) STS_SET,
michael@0 177 (uint32_t) nsIPermissionManager::EXPIRE_TIME,
michael@0 178 expiretime,
michael@0 179 isPrivate);
michael@0 180 NS_ENSURE_SUCCESS(rv, rv);
michael@0 181 } else { // !includeSubdomains
michael@0 182 nsAutoCString hostname;
michael@0 183 rv = GetHost(aSourceURI, hostname);
michael@0 184 NS_ENSURE_SUCCESS(rv, rv);
michael@0 185
michael@0 186 SSSLOG(("SSS: subdomains permission UNSET, removing any existing ones\n"));
michael@0 187 rv = RemovePermission(hostname, STS_SUBDOMAIN_PERMISSION, isPrivate);
michael@0 188 NS_ENSURE_SUCCESS(rv, rv);
michael@0 189 }
michael@0 190 return NS_OK;
michael@0 191 }
michael@0 192
michael@0 193 NS_IMETHODIMP
michael@0 194 nsSiteSecurityService::RemoveState(uint32_t aType, nsIURI* aURI, uint32_t aFlags)
michael@0 195 {
michael@0 196 // Should be called on the main thread (or via proxy) since the permission
michael@0 197 // manager is used and it's not threadsafe.
michael@0 198 NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED);
michael@0 199 // Only HSTS is supported at the moment.
michael@0 200 NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS,
michael@0 201 NS_ERROR_NOT_IMPLEMENTED);
michael@0 202
michael@0 203 nsAutoCString hostname;
michael@0 204 nsresult rv = GetHost(aURI, hostname);
michael@0 205 NS_ENSURE_SUCCESS(rv, rv);
michael@0 206
michael@0 207 bool isPrivate = aFlags & nsISocketProvider::NO_PERMANENT_STORAGE;
michael@0 208
michael@0 209 rv = RemovePermission(hostname, STS_PERMISSION, isPrivate);
michael@0 210 NS_ENSURE_SUCCESS(rv, rv);
michael@0 211 SSSLOG(("SSS: deleted maxage permission\n"));
michael@0 212
michael@0 213 rv = RemovePermission(hostname, STS_SUBDOMAIN_PERMISSION, isPrivate);
michael@0 214 NS_ENSURE_SUCCESS(rv, rv);
michael@0 215 SSSLOG(("SSS: deleted subdomains permission\n"));
michael@0 216
michael@0 217 return NS_OK;
michael@0 218 }
michael@0 219
michael@0 220 static bool
michael@0 221 HostIsIPAddress(const char *hostname)
michael@0 222 {
michael@0 223 PRNetAddr hostAddr;
michael@0 224 return (PR_StringToNetAddr(hostname, &hostAddr) == PR_SUCCESS);
michael@0 225 }
michael@0 226
michael@0 227 NS_IMETHODIMP
michael@0 228 nsSiteSecurityService::ProcessHeader(uint32_t aType,
michael@0 229 nsIURI* aSourceURI,
michael@0 230 const char* aHeader,
michael@0 231 uint32_t aFlags,
michael@0 232 uint64_t *aMaxAge,
michael@0 233 bool *aIncludeSubdomains)
michael@0 234 {
michael@0 235 // Should be called on the main thread (or via proxy) since the permission
michael@0 236 // manager is used and it's not threadsafe.
michael@0 237 NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED);
michael@0 238 // Only HSTS is supported at the moment.
michael@0 239 NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS,
michael@0 240 NS_ERROR_NOT_IMPLEMENTED);
michael@0 241
michael@0 242 if (aMaxAge != nullptr) {
michael@0 243 *aMaxAge = 0;
michael@0 244 }
michael@0 245
michael@0 246 if (aIncludeSubdomains != nullptr) {
michael@0 247 *aIncludeSubdomains = false;
michael@0 248 }
michael@0 249
michael@0 250 nsAutoCString host;
michael@0 251 nsresult rv = GetHost(aSourceURI, host);
michael@0 252 NS_ENSURE_SUCCESS(rv, rv);
michael@0 253 if (HostIsIPAddress(host.get())) {
michael@0 254 /* Don't process headers if a site is accessed by IP address. */
michael@0 255 return NS_OK;
michael@0 256 }
michael@0 257
michael@0 258 char * header = NS_strdup(aHeader);
michael@0 259 if (!header) return NS_ERROR_OUT_OF_MEMORY;
michael@0 260 rv = ProcessHeaderMutating(aType, aSourceURI, header, aFlags,
michael@0 261 aMaxAge, aIncludeSubdomains);
michael@0 262 NS_Free(header);
michael@0 263 return rv;
michael@0 264 }
michael@0 265
michael@0 266 nsresult
michael@0 267 nsSiteSecurityService::ProcessHeaderMutating(uint32_t aType,
michael@0 268 nsIURI* aSourceURI,
michael@0 269 char* aHeader,
michael@0 270 uint32_t aFlags,
michael@0 271 uint64_t *aMaxAge,
michael@0 272 bool *aIncludeSubdomains)
michael@0 273 {
michael@0 274 SSSLOG(("SSS: processing header '%s'", aHeader));
michael@0 275
michael@0 276 // "Strict-Transport-Security" ":" OWS
michael@0 277 // STS-d *( OWS ";" OWS STS-d OWS)
michael@0 278 //
michael@0 279 // ; STS directive
michael@0 280 // STS-d = maxAge / includeSubDomains
michael@0 281 //
michael@0 282 // maxAge = "max-age" "=" delta-seconds v-ext
michael@0 283 //
michael@0 284 // includeSubDomains = [ "includeSubDomains" ]
michael@0 285 //
michael@0 286 // The order of the directives is not significant.
michael@0 287 // All directives must appear only once.
michael@0 288 // Directive names are case-insensitive.
michael@0 289 // The entire header is invalid if a directive not conforming to the
michael@0 290 // syntax is encountered.
michael@0 291 // Unrecognized directives (that are otherwise syntactically valid) are
michael@0 292 // ignored, and the rest of the header is parsed as normal.
michael@0 293
michael@0 294 bool foundMaxAge = false;
michael@0 295 bool foundIncludeSubdomains = false;
michael@0 296 bool foundUnrecognizedDirective = false;
michael@0 297 int64_t maxAge = 0;
michael@0 298
michael@0 299 NS_NAMED_LITERAL_CSTRING(max_age_var, "max-age");
michael@0 300 NS_NAMED_LITERAL_CSTRING(include_subd_var, "includesubdomains");
michael@0 301
michael@0 302
michael@0 303 nsSecurityHeaderParser parser(aHeader);
michael@0 304 nsresult rv = parser.Parse();
michael@0 305 if (NS_FAILED(rv)) {
michael@0 306 SSSLOG(("SSS: could not parse header"));
michael@0 307 return rv;
michael@0 308 }
michael@0 309 mozilla::LinkedList<nsSecurityHeaderDirective> *directives = parser.GetDirectives();
michael@0 310
michael@0 311 for (nsSecurityHeaderDirective *directive = directives->getFirst();
michael@0 312 directive != nullptr; directive = directive->getNext()) {
michael@0 313 if (directive->mName.Length() == max_age_var.Length() &&
michael@0 314 directive->mName.EqualsIgnoreCase(max_age_var.get(),
michael@0 315 max_age_var.Length())) {
michael@0 316 if (foundMaxAge) {
michael@0 317 SSSLOG(("SSS: found two max-age directives"));
michael@0 318 return NS_ERROR_FAILURE;
michael@0 319 }
michael@0 320
michael@0 321 SSSLOG(("SSS: found max-age directive"));
michael@0 322 foundMaxAge = true;
michael@0 323
michael@0 324 size_t len = directive->mValue.Length();
michael@0 325 for (size_t i = 0; i < len; i++) {
michael@0 326 char chr = directive->mValue.CharAt(i);
michael@0 327 if (chr < '0' || chr > '9') {
michael@0 328 SSSLOG(("SSS: invalid value for max-age directive"));
michael@0 329 return NS_ERROR_FAILURE;
michael@0 330 }
michael@0 331 }
michael@0 332
michael@0 333 if (PR_sscanf(directive->mValue.get(), "%lld", &maxAge) != 1) {
michael@0 334 SSSLOG(("SSS: could not parse delta-seconds"));
michael@0 335 return NS_ERROR_FAILURE;
michael@0 336 }
michael@0 337
michael@0 338 SSSLOG(("SSS: parsed delta-seconds: %lld", maxAge));
michael@0 339 } else if (directive->mName.Length() == include_subd_var.Length() &&
michael@0 340 directive->mName.EqualsIgnoreCase(include_subd_var.get(),
michael@0 341 include_subd_var.Length())) {
michael@0 342 if (foundIncludeSubdomains) {
michael@0 343 SSSLOG(("SSS: found two includeSubdomains directives"));
michael@0 344 return NS_ERROR_FAILURE;
michael@0 345 }
michael@0 346
michael@0 347 SSSLOG(("SSS: found includeSubdomains directive"));
michael@0 348 foundIncludeSubdomains = true;
michael@0 349
michael@0 350 if (directive->mValue.Length() != 0) {
michael@0 351 SSSLOG(("SSS: includeSubdomains directive unexpectedly had value '%s'", directive->mValue.get()));
michael@0 352 return NS_ERROR_FAILURE;
michael@0 353 }
michael@0 354 } else {
michael@0 355 SSSLOG(("SSS: ignoring unrecognized directive '%s'", directive->mName.get()));
michael@0 356 foundUnrecognizedDirective = true;
michael@0 357 }
michael@0 358 }
michael@0 359
michael@0 360 // after processing all the directives, make sure we came across max-age
michael@0 361 // somewhere.
michael@0 362 if (!foundMaxAge) {
michael@0 363 SSSLOG(("SSS: did not encounter required max-age directive"));
michael@0 364 return NS_ERROR_FAILURE;
michael@0 365 }
michael@0 366
michael@0 367 // record the successfully parsed header data.
michael@0 368 SetState(aType, aSourceURI, maxAge, foundIncludeSubdomains, aFlags);
michael@0 369
michael@0 370 if (aMaxAge != nullptr) {
michael@0 371 *aMaxAge = (uint64_t)maxAge;
michael@0 372 }
michael@0 373
michael@0 374 if (aIncludeSubdomains != nullptr) {
michael@0 375 *aIncludeSubdomains = foundIncludeSubdomains;
michael@0 376 }
michael@0 377
michael@0 378 return foundUnrecognizedDirective ?
michael@0 379 NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA :
michael@0 380 NS_OK;
michael@0 381 }
michael@0 382
michael@0 383 NS_IMETHODIMP
michael@0 384 nsSiteSecurityService::IsSecureHost(uint32_t aType, const char* aHost,
michael@0 385 uint32_t aFlags, bool* aResult)
michael@0 386 {
michael@0 387 // Should be called on the main thread (or via proxy) since the permission
michael@0 388 // manager is used and it's not threadsafe.
michael@0 389 NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED);
michael@0 390 // Only HSTS is supported at the moment.
michael@0 391 NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS,
michael@0 392 NS_ERROR_NOT_IMPLEMENTED);
michael@0 393
michael@0 394 /* An IP address never qualifies as a secure URI. */
michael@0 395 if (HostIsIPAddress(aHost)) {
michael@0 396 *aResult = false;
michael@0 397 return NS_OK;
michael@0 398 }
michael@0 399
michael@0 400 nsCOMPtr<nsIURI> uri;
michael@0 401 nsDependentCString hostString(aHost);
michael@0 402 nsresult rv = NS_NewURI(getter_AddRefs(uri),
michael@0 403 NS_LITERAL_CSTRING("https://") + hostString);
michael@0 404 NS_ENSURE_SUCCESS(rv, rv);
michael@0 405 return IsSecureURI(aType, uri, aFlags, aResult);
michael@0 406 }
michael@0 407
michael@0 408 int STSPreloadCompare(const void *key, const void *entry)
michael@0 409 {
michael@0 410 const char *keyStr = (const char *)key;
michael@0 411 const nsSTSPreload *preloadEntry = (const nsSTSPreload *)entry;
michael@0 412 return strcmp(keyStr, preloadEntry->mHost);
michael@0 413 }
michael@0 414
michael@0 415 // Returns the preload list entry for the given host, if it exists.
michael@0 416 // Only does exact host matching - the user must decide how to use the returned
michael@0 417 // data. May return null.
michael@0 418 const nsSTSPreload *
michael@0 419 nsSiteSecurityService::GetPreloadListEntry(const char *aHost)
michael@0 420 {
michael@0 421 PRTime currentTime = PR_Now();
michael@0 422 int32_t timeOffset = 0;
michael@0 423 nsresult rv = mozilla::Preferences::GetInt("test.currentTimeOffsetSeconds",
michael@0 424 &timeOffset);
michael@0 425 if (NS_SUCCEEDED(rv)) {
michael@0 426 currentTime += (PRTime(timeOffset) * PR_USEC_PER_SEC);
michael@0 427 }
michael@0 428
michael@0 429 if (mUsePreloadList && currentTime < gPreloadListExpirationTime) {
michael@0 430 return (const nsSTSPreload *) bsearch(aHost,
michael@0 431 kSTSPreloadList,
michael@0 432 mozilla::ArrayLength(kSTSPreloadList),
michael@0 433 sizeof(nsSTSPreload),
michael@0 434 STSPreloadCompare);
michael@0 435 }
michael@0 436
michael@0 437 return nullptr;
michael@0 438 }
michael@0 439
michael@0 440 NS_IMETHODIMP
michael@0 441 nsSiteSecurityService::IsSecureURI(uint32_t aType, nsIURI* aURI,
michael@0 442 uint32_t aFlags, bool* aResult)
michael@0 443 {
michael@0 444 // Should be called on the main thread (or via proxy) since the permission
michael@0 445 // manager is used and it's not threadsafe.
michael@0 446 NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED);
michael@0 447 // Only HSTS is supported at the moment.
michael@0 448 NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS,
michael@0 449 NS_ERROR_NOT_IMPLEMENTED);
michael@0 450
michael@0 451 // set default in case if we can't find any STS information
michael@0 452 *aResult = false;
michael@0 453
michael@0 454 nsAutoCString host;
michael@0 455 nsresult rv = GetHost(aURI, host);
michael@0 456 NS_ENSURE_SUCCESS(rv, rv);
michael@0 457
michael@0 458 /* An IP address never qualifies as a secure URI. */
michael@0 459 if (HostIsIPAddress(host.BeginReading())) {
michael@0 460 return NS_OK;
michael@0 461 }
michael@0 462
michael@0 463 // Holepunch chart.apis.google.com and subdomains.
michael@0 464 if (host.Equals(NS_LITERAL_CSTRING("chart.apis.google.com")) ||
michael@0 465 StringEndsWith(host, NS_LITERAL_CSTRING(".chart.apis.google.com"))) {
michael@0 466 return NS_OK;
michael@0 467 }
michael@0 468
michael@0 469 const nsSTSPreload *preload = nullptr;
michael@0 470 nsSSSHostEntry *pbEntry = nullptr;
michael@0 471
michael@0 472 bool isPrivate = aFlags & nsISocketProvider::NO_PERMANENT_STORAGE;
michael@0 473 if (isPrivate) {
michael@0 474 pbEntry = mPrivateModeHostTable.GetEntry(host.get());
michael@0 475 }
michael@0 476
michael@0 477 nsCOMPtr<nsIPrincipal> principal;
michael@0 478 rv = GetPrincipalForURI(aURI, getter_AddRefs(principal));
michael@0 479 NS_ENSURE_SUCCESS(rv, rv);
michael@0 480
michael@0 481 uint32_t permMgrPermission;
michael@0 482 rv = mPermMgr->TestExactPermissionFromPrincipal(principal, STS_PERMISSION,
michael@0 483 &permMgrPermission);
michael@0 484 NS_ENSURE_SUCCESS(rv, rv);
michael@0 485
michael@0 486 // First check the exact host. This involves first checking for an entry in
michael@0 487 // the private browsing table. If that entry exists, we don't want to check
michael@0 488 // in either the permission manager or the preload list. We only want to use
michael@0 489 // the stored permission if it is not a knockout entry, however.
michael@0 490 // Additionally, if it is a knockout entry, we want to stop looking for data
michael@0 491 // on the host, because the knockout entry indicates "we have no information
michael@0 492 // regarding the sts status of this host".
michael@0 493 if (pbEntry && pbEntry->mStsPermission != STS_UNSET) {
michael@0 494 SSSLOG(("Found private browsing table entry for %s", host.get()));
michael@0 495 if (!pbEntry->IsExpired() && pbEntry->mStsPermission == STS_SET) {
michael@0 496 *aResult = true;
michael@0 497 return NS_OK;
michael@0 498 }
michael@0 499 }
michael@0 500 // Next we look in the permission manager. Same story here regarding
michael@0 501 // knockout entries.
michael@0 502 else if (permMgrPermission != STS_UNSET) {
michael@0 503 SSSLOG(("Found permission manager entry for %s", host.get()));
michael@0 504 if (permMgrPermission == STS_SET) {
michael@0 505 *aResult = true;
michael@0 506 return NS_OK;
michael@0 507 }
michael@0 508 }
michael@0 509 // Finally look in the preloaded list. This is the exact host,
michael@0 510 // so if an entry exists at all, this host is sts.
michael@0 511 else if (GetPreloadListEntry(host.get())) {
michael@0 512 SSSLOG(("%s is a preloaded STS host", host.get()));
michael@0 513 *aResult = true;
michael@0 514 return NS_OK;
michael@0 515 }
michael@0 516
michael@0 517 // Used for testing permissions as we walk up the domain tree.
michael@0 518 nsCOMPtr<nsIURI> domainWalkURI;
michael@0 519 nsCOMPtr<nsIPrincipal> domainWalkPrincipal;
michael@0 520 const char *subdomain;
michael@0 521
michael@0 522 SSSLOG(("no HSTS data for %s found, walking up domain", host.get()));
michael@0 523 uint32_t offset = 0;
michael@0 524 for (offset = host.FindChar('.', offset) + 1;
michael@0 525 offset > 0;
michael@0 526 offset = host.FindChar('.', offset) + 1) {
michael@0 527
michael@0 528 subdomain = host.get() + offset;
michael@0 529
michael@0 530 // If we get an empty string, don't continue.
michael@0 531 if (strlen(subdomain) < 1) {
michael@0 532 break;
michael@0 533 }
michael@0 534
michael@0 535 if (isPrivate) {
michael@0 536 pbEntry = mPrivateModeHostTable.GetEntry(subdomain);
michael@0 537 }
michael@0 538
michael@0 539 // normalize all URIs with https://
michael@0 540 rv = NS_NewURI(getter_AddRefs(domainWalkURI),
michael@0 541 NS_LITERAL_CSTRING("https://") + Substring(host, offset));
michael@0 542 NS_ENSURE_SUCCESS(rv, rv);
michael@0 543
michael@0 544 rv = GetPrincipalForURI(domainWalkURI, getter_AddRefs(domainWalkPrincipal));
michael@0 545 NS_ENSURE_SUCCESS(rv, rv);
michael@0 546
michael@0 547 rv = mPermMgr->TestExactPermissionFromPrincipal(domainWalkPrincipal,
michael@0 548 STS_PERMISSION,
michael@0 549 &permMgrPermission);
michael@0 550 NS_ENSURE_SUCCESS(rv, rv);
michael@0 551
michael@0 552 // Do the same thing as with the exact host, except now we're looking at
michael@0 553 // ancestor domains of the original host. So, we have to look at the
michael@0 554 // include subdomains permissions (although we still have to check for the
michael@0 555 // STS_PERMISSION first to check that this is an sts host and not a
michael@0 556 // knockout entry - and again, if it is a knockout entry, we stop looking
michael@0 557 // for data on it and skip to the next higher up ancestor domain).
michael@0 558 if (pbEntry && pbEntry->mStsPermission != STS_UNSET) {
michael@0 559 SSSLOG(("Found private browsing table entry for %s", subdomain));
michael@0 560 if (!pbEntry->IsExpired() && pbEntry->mStsPermission == STS_SET) {
michael@0 561 *aResult = pbEntry->mIncludeSubdomains;
michael@0 562 break;
michael@0 563 }
michael@0 564 }
michael@0 565 else if (permMgrPermission != STS_UNSET) {
michael@0 566 SSSLOG(("Found permission manager entry for %s", subdomain));
michael@0 567 if (permMgrPermission == STS_SET) {
michael@0 568 uint32_t subdomainPermission;
michael@0 569 rv = mPermMgr->TestExactPermissionFromPrincipal(domainWalkPrincipal,
michael@0 570 STS_SUBDOMAIN_PERMISSION,
michael@0 571 &subdomainPermission);
michael@0 572 NS_ENSURE_SUCCESS(rv, rv);
michael@0 573 *aResult = (subdomainPermission == STS_SET);
michael@0 574 break;
michael@0 575 }
michael@0 576 }
michael@0 577 // This is an ancestor, so if we get a match, we have to check if the
michael@0 578 // preloaded entry includes subdomains.
michael@0 579 else if ((preload = GetPreloadListEntry(subdomain)) != nullptr) {
michael@0 580 if (preload->mIncludeSubdomains) {
michael@0 581 SSSLOG(("%s is a preloaded STS host", subdomain));
michael@0 582 *aResult = true;
michael@0 583 break;
michael@0 584 }
michael@0 585 }
michael@0 586
michael@0 587 SSSLOG(("no HSTS data for %s found, walking up domain", subdomain));
michael@0 588 }
michael@0 589
michael@0 590 // Use whatever we ended up with, which defaults to false.
michael@0 591 return NS_OK;
michael@0 592 }
michael@0 593
michael@0 594
michael@0 595 // Verify the trustworthiness of the security info (are there any cert errors?)
michael@0 596 NS_IMETHODIMP
michael@0 597 nsSiteSecurityService::ShouldIgnoreHeaders(nsISupports* aSecurityInfo,
michael@0 598 bool* aResult)
michael@0 599 {
michael@0 600 nsresult rv;
michael@0 601 bool tlsIsBroken = false;
michael@0 602 nsCOMPtr<nsISSLStatusProvider> sslprov = do_QueryInterface(aSecurityInfo);
michael@0 603 NS_ENSURE_TRUE(sslprov, NS_ERROR_FAILURE);
michael@0 604
michael@0 605 nsCOMPtr<nsISSLStatus> sslstat;
michael@0 606 rv = sslprov->GetSSLStatus(getter_AddRefs(sslstat));
michael@0 607 NS_ENSURE_SUCCESS(rv, rv);
michael@0 608 NS_ENSURE_TRUE(sslstat, NS_ERROR_FAILURE);
michael@0 609
michael@0 610 bool trustcheck;
michael@0 611 rv = sslstat->GetIsDomainMismatch(&trustcheck);
michael@0 612 NS_ENSURE_SUCCESS(rv, rv);
michael@0 613 tlsIsBroken = tlsIsBroken || trustcheck;
michael@0 614
michael@0 615 rv = sslstat->GetIsNotValidAtThisTime(&trustcheck);
michael@0 616 NS_ENSURE_SUCCESS(rv, rv);
michael@0 617 tlsIsBroken = tlsIsBroken || trustcheck;
michael@0 618
michael@0 619 rv = sslstat->GetIsUntrusted(&trustcheck);
michael@0 620 NS_ENSURE_SUCCESS(rv, rv);
michael@0 621 tlsIsBroken = tlsIsBroken || trustcheck;
michael@0 622
michael@0 623 *aResult = tlsIsBroken;
michael@0 624 return NS_OK;
michael@0 625 }
michael@0 626
michael@0 627 //------------------------------------------------------------
michael@0 628 // nsSiteSecurityService::nsIObserver
michael@0 629 //------------------------------------------------------------
michael@0 630
michael@0 631 NS_IMETHODIMP
michael@0 632 nsSiteSecurityService::Observe(nsISupports *subject,
michael@0 633 const char *topic,
michael@0 634 const char16_t *data)
michael@0 635 {
michael@0 636 if (strcmp(topic, "last-pb-context-exited") == 0) {
michael@0 637 mPrivateModeHostTable.Clear();
michael@0 638 }
michael@0 639 else if (strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
michael@0 640 mUsePreloadList = mozilla::Preferences::GetBool("network.stricttransportsecurity.preloadlist", true);
michael@0 641 }
michael@0 642
michael@0 643 return NS_OK;
michael@0 644 }
michael@0 645
michael@0 646 //------------------------------------------------------------
michael@0 647 // Functions to overlay the permission manager calls in case
michael@0 648 // we're in private browsing mode.
michael@0 649 //------------------------------------------------------------
michael@0 650 nsresult
michael@0 651 nsSiteSecurityService::AddPermission(nsIURI *aURI,
michael@0 652 const char *aType,
michael@0 653 uint32_t aPermission,
michael@0 654 uint32_t aExpireType,
michael@0 655 int64_t aExpireTime,
michael@0 656 bool aIsPrivate)
michael@0 657 {
michael@0 658 // Private mode doesn't address user-set (EXPIRE_NEVER) permissions: let
michael@0 659 // those be stored persistently.
michael@0 660 if (!aIsPrivate || aExpireType == nsIPermissionManager::EXPIRE_NEVER) {
michael@0 661 // Not in private mode, or manually-set permission
michael@0 662 nsCOMPtr<nsIPrincipal> principal;
michael@0 663 nsresult rv = GetPrincipalForURI(aURI, getter_AddRefs(principal));
michael@0 664 NS_ENSURE_SUCCESS(rv, rv);
michael@0 665
michael@0 666 return mPermMgr->AddFromPrincipal(principal, aType, aPermission,
michael@0 667 aExpireType, aExpireTime);
michael@0 668 }
michael@0 669
michael@0 670 nsAutoCString host;
michael@0 671 nsresult rv = GetHost(aURI, host);
michael@0 672 NS_ENSURE_SUCCESS(rv, rv);
michael@0 673 SSSLOG(("AddPermission for entry for %s", host.get()));
michael@0 674
michael@0 675 // Update in mPrivateModeHostTable only, so any changes will be rolled
michael@0 676 // back when exiting private mode.
michael@0 677
michael@0 678 // Note: EXPIRE_NEVER permissions should trump anything that shows up in
michael@0 679 // the HTTP header, so if there's an EXPIRE_NEVER permission already
michael@0 680 // don't store anything new.
michael@0 681 // Currently there's no way to get the type of expiry out of the
michael@0 682 // permission manager, but that's okay since there's nothing that stores
michael@0 683 // EXPIRE_NEVER permissions.
michael@0 684
michael@0 685 // PutEntry returns an existing entry if there already is one, or it
michael@0 686 // creates a new one if there isn't.
michael@0 687 nsSSSHostEntry* entry = mPrivateModeHostTable.PutEntry(host.get());
michael@0 688 if (!entry) {
michael@0 689 return NS_ERROR_OUT_OF_MEMORY;
michael@0 690 }
michael@0 691 SSSLOG(("Created private mode entry for %s", host.get()));
michael@0 692
michael@0 693 // AddPermission() will be called twice if the STS header encountered has
michael@0 694 // includeSubdomains (first for the main permission and second for the
michael@0 695 // subdomains permission). If AddPermission() gets called a second time
michael@0 696 // with the STS_SUBDOMAIN_PERMISSION, we just have to flip that bit in
michael@0 697 // the nsSSSHostEntry.
michael@0 698 if (strcmp(aType, STS_SUBDOMAIN_PERMISSION) == 0) {
michael@0 699 entry->mIncludeSubdomains = true;
michael@0 700 }
michael@0 701 else if (strcmp(aType, STS_PERMISSION) == 0) {
michael@0 702 entry->mStsPermission = aPermission;
michael@0 703 }
michael@0 704
michael@0 705 // Also refresh the expiration time.
michael@0 706 entry->SetExpireTime(aExpireTime);
michael@0 707 return NS_OK;
michael@0 708 }
michael@0 709
michael@0 710 nsresult
michael@0 711 nsSiteSecurityService::RemovePermission(const nsCString &aHost,
michael@0 712 const char *aType,
michael@0 713 bool aIsPrivate)
michael@0 714 {
michael@0 715 // Build up a principal for use with the permission manager.
michael@0 716 // normalize all URIs with https://
michael@0 717 nsCOMPtr<nsIURI> uri;
michael@0 718 nsresult rv = NS_NewURI(getter_AddRefs(uri),
michael@0 719 NS_LITERAL_CSTRING("https://") + aHost);
michael@0 720 NS_ENSURE_SUCCESS(rv, rv);
michael@0 721
michael@0 722 nsCOMPtr<nsIPrincipal> principal;
michael@0 723 rv = GetPrincipalForURI(uri, getter_AddRefs(principal));
michael@0 724 NS_ENSURE_SUCCESS(rv, rv);
michael@0 725
michael@0 726 if (!aIsPrivate) {
michael@0 727 // Not in private mode: remove permissions persistently.
michael@0 728 // This means setting the permission to STS_KNOCKOUT in case
michael@0 729 // this host is on the preload list (so we can override it).
michael@0 730 return mPermMgr->AddFromPrincipal(principal, aType,
michael@0 731 STS_KNOCKOUT,
michael@0 732 nsIPermissionManager::EXPIRE_NEVER, 0);
michael@0 733 }
michael@0 734
michael@0 735 // Make changes in mPrivateModeHostTable only, so any changes will be
michael@0 736 // rolled back when exiting private mode.
michael@0 737 nsSSSHostEntry* entry = mPrivateModeHostTable.GetEntry(aHost.get());
michael@0 738
michael@0 739 if (!entry) {
michael@0 740 entry = mPrivateModeHostTable.PutEntry(aHost.get());
michael@0 741 if (!entry) {
michael@0 742 return NS_ERROR_OUT_OF_MEMORY;
michael@0 743 }
michael@0 744 SSSLOG(("Created private mode deleted mask for %s", aHost.get()));
michael@0 745 }
michael@0 746
michael@0 747 if (strcmp(aType, STS_PERMISSION) == 0) {
michael@0 748 entry->mStsPermission = STS_KNOCKOUT;
michael@0 749 }
michael@0 750 else if (strcmp(aType, STS_SUBDOMAIN_PERMISSION) == 0) {
michael@0 751 entry->mIncludeSubdomains = false;
michael@0 752 }
michael@0 753
michael@0 754 return NS_OK;
michael@0 755 }

mercurial