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