Line data Source code
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 "nsSiteSecurityService.h"
6 :
7 : #include "CertVerifier.h"
8 : #include "PublicKeyPinningService.h"
9 : #include "ScopedNSSTypes.h"
10 : #include "SharedCertVerifier.h"
11 : #include "mozilla/Assertions.h"
12 : #include "mozilla/Attributes.h"
13 : #include "mozilla/Base64.h"
14 : #include "mozilla/LinkedList.h"
15 : #include "mozilla/Logging.h"
16 : #include "mozilla/Preferences.h"
17 : #include "mozilla/Tokenizer.h"
18 : #include "mozilla/dom/PContent.h"
19 : #include "mozilla/dom/ToJSValue.h"
20 : #include "nsArrayEnumerator.h"
21 : #include "nsCOMArray.h"
22 : #include "nsISSLStatus.h"
23 : #include "nsIScriptSecurityManager.h"
24 : #include "nsISocketProvider.h"
25 : #include "nsIURI.h"
26 : #include "nsIX509Cert.h"
27 : #include "nsNSSComponent.h"
28 : #include "nsNetUtil.h"
29 : #include "nsPromiseFlatString.h"
30 : #include "nsReadableUtils.h"
31 : #include "nsSecurityHeaderParser.h"
32 : #include "nsThreadUtils.h"
33 : #include "nsVariant.h"
34 : #include "nsXULAppAPI.h"
35 : #include "prnetdb.h"
36 :
37 : // A note about the preload list:
38 : // When a site specifically disables HSTS by sending a header with
39 : // 'max-age: 0', we keep a "knockout" value that means "we have no information
40 : // regarding the HSTS state of this host" (any ancestor of "this host" can still
41 : // influence its HSTS status via include subdomains, however).
42 : // This prevents the preload list from overriding the site's current
43 : // desired HSTS status.
44 : #include "nsSTSPreloadList.inc"
45 :
46 : using namespace mozilla;
47 : using namespace mozilla::psm;
48 :
49 : static LazyLogModule gSSSLog("nsSSService");
50 :
51 : #define SSSLOG(args) MOZ_LOG(gSSSLog, mozilla::LogLevel::Debug, args)
52 :
53 : const char kHSTSKeySuffix[] = ":HSTS";
54 : const char kHPKPKeySuffix[] = ":HPKP";
55 :
56 : ////////////////////////////////////////////////////////////////////////////////
57 :
58 32 : NS_IMPL_ISUPPORTS(SiteHSTSState, nsISiteSecurityState, nsISiteHSTSState)
59 :
60 : namespace {
61 :
62 : static bool
63 0 : stringIsBase64EncodingOf256bitValue(const nsCString& encodedString) {
64 0 : nsAutoCString binaryValue;
65 0 : nsresult rv = Base64Decode(encodedString, binaryValue);
66 0 : if (NS_FAILED(rv)) {
67 0 : return false;
68 : }
69 :
70 0 : return binaryValue.Length() == SHA256_LENGTH;
71 : }
72 :
73 16 : class SSSTokenizer final : public Tokenizer
74 : {
75 : public:
76 16 : explicit SSSTokenizer(const nsACString& source)
77 16 : : Tokenizer(source)
78 : {
79 16 : }
80 :
81 : MOZ_MUST_USE bool
82 0 : ReadBool(/*out*/ bool& value)
83 : {
84 : uint8_t rawValue;
85 0 : if (!ReadInteger(&rawValue)) {
86 0 : return false;
87 : }
88 :
89 0 : if (rawValue != 0 && rawValue != 1) {
90 0 : return false;
91 : }
92 :
93 0 : value = (rawValue == 1);
94 0 : return true;
95 : }
96 :
97 : MOZ_MUST_USE bool
98 0 : ReadState(/*out*/ SecurityPropertyState& state)
99 : {
100 : uint32_t rawValue;
101 0 : if (!ReadInteger(&rawValue)) {
102 0 : return false;
103 : }
104 :
105 0 : state = static_cast<SecurityPropertyState>(rawValue);
106 0 : switch (state) {
107 : case SecurityPropertyKnockout:
108 : case SecurityPropertyNegative:
109 : case SecurityPropertySet:
110 : case SecurityPropertyUnset:
111 0 : break;
112 : default:
113 0 : return false;
114 : }
115 :
116 0 : return true;
117 : }
118 :
119 : MOZ_MUST_USE bool
120 0 : ReadSource(/*out*/ SecurityPropertySource& source)
121 : {
122 : uint32_t rawValue;
123 0 : if (!ReadInteger(&rawValue)) {
124 0 : return false;
125 : }
126 :
127 0 : source = static_cast<SecurityPropertySource>(rawValue);
128 0 : switch (source) {
129 : case SourceUnknown:
130 : case SourcePreload:
131 : case SourceOrganic:
132 : case SourceHSTSPriming:
133 0 : break;
134 : default:
135 0 : return false;
136 : }
137 :
138 0 : return true;
139 : }
140 :
141 : // Note: Ideally, this method would be able to read SHA256 strings without
142 : // reading all the way to EOF. Unfortunately, if a token starts with digits
143 : // mozilla::Tokenizer will by default not consider the digits part of the
144 : // string. This can be worked around by making mozilla::Tokenizer consider
145 : // digit characters as "word" characters as well, but this can't be changed at
146 : // run time, meaning parsing digits as a number will fail.
147 : MOZ_MUST_USE bool
148 0 : ReadUntilEOFAsSHA256Keys(/*out*/ nsTArray<nsCString>& keys)
149 : {
150 0 : nsAutoCString mergedKeys;
151 0 : if (!ReadUntil(Token::EndOfFile(), mergedKeys, EXCLUDE_LAST)) {
152 0 : return false;
153 : }
154 :
155 : // This check makes sure the Substring() calls below are always valid.
156 : static const uint32_t SHA256Base64Len = 44;
157 0 : if (mergedKeys.Length() % SHA256Base64Len != 0) {
158 0 : return false;
159 : }
160 :
161 0 : for (uint32_t i = 0; i < mergedKeys.Length(); i += SHA256Base64Len) {
162 0 : nsAutoCString key(Substring(mergedKeys, i, SHA256Base64Len));
163 0 : if (!stringIsBase64EncodingOf256bitValue(key)) {
164 0 : return false;
165 : }
166 0 : keys.AppendElement(key);
167 : }
168 :
169 0 : return !keys.IsEmpty();
170 : }
171 : };
172 :
173 : // Parses a state string like "1500918564034,1,1" into its constituent parts.
174 : bool
175 16 : ParseHSTSState(const nsCString& stateString,
176 : /*out*/ PRTime& expireTime,
177 : /*out*/ SecurityPropertyState& state,
178 : /*out*/ bool& includeSubdomains,
179 : /*out*/ SecurityPropertySource& source)
180 : {
181 32 : SSSTokenizer tokenizer(stateString);
182 16 : SSSLOG(("Parsing state from %s", stateString.get()));
183 :
184 16 : if (!tokenizer.ReadInteger(&expireTime)) {
185 16 : return false;
186 : }
187 :
188 0 : if (!tokenizer.CheckChar(',')) {
189 0 : return false;
190 : }
191 :
192 0 : if (!tokenizer.ReadState(state)) {
193 0 : return false;
194 : }
195 :
196 0 : if (!tokenizer.CheckChar(',')) {
197 0 : return false;
198 : }
199 :
200 0 : if (!tokenizer.ReadBool(includeSubdomains)) {
201 0 : return false;
202 : }
203 :
204 0 : source = SourceUnknown;
205 0 : if (tokenizer.CheckChar(',')) {
206 0 : if (!tokenizer.ReadSource(source)) {
207 0 : return false;
208 : }
209 : }
210 :
211 0 : return tokenizer.CheckEOF();
212 : }
213 :
214 : } // namespace
215 :
216 16 : SiteHSTSState::SiteHSTSState(const nsCString& aHost,
217 : const OriginAttributes& aOriginAttributes,
218 16 : const nsCString& aStateString)
219 : : mHostname(aHost)
220 : , mOriginAttributes(aOriginAttributes)
221 : , mHSTSExpireTime(0)
222 : , mHSTSState(SecurityPropertyUnset)
223 : , mHSTSIncludeSubdomains(false)
224 16 : , mHSTSSource(SourceUnknown)
225 : {
226 16 : bool valid = ParseHSTSState(aStateString, mHSTSExpireTime, mHSTSState,
227 16 : mHSTSIncludeSubdomains, mHSTSSource);
228 16 : if (!valid) {
229 16 : SSSLOG(("%s is not a valid SiteHSTSState", aStateString.get()));
230 16 : mHSTSExpireTime = 0;
231 16 : mHSTSState = SecurityPropertyUnset;
232 16 : mHSTSIncludeSubdomains = false;
233 16 : mHSTSSource = SourceUnknown;
234 : }
235 16 : }
236 :
237 0 : SiteHSTSState::SiteHSTSState(const nsCString& aHost,
238 : const OriginAttributes& aOriginAttributes,
239 : PRTime aHSTSExpireTime,
240 : SecurityPropertyState aHSTSState,
241 : bool aHSTSIncludeSubdomains,
242 0 : SecurityPropertySource aSource)
243 :
244 : : mHostname(aHost)
245 : , mOriginAttributes(aOriginAttributes)
246 : , mHSTSExpireTime(aHSTSExpireTime)
247 : , mHSTSState(aHSTSState)
248 : , mHSTSIncludeSubdomains(aHSTSIncludeSubdomains)
249 0 : , mHSTSSource(aSource)
250 : {
251 0 : }
252 :
253 : void
254 0 : SiteHSTSState::ToString(nsCString& aString)
255 : {
256 0 : aString.Truncate();
257 0 : aString.AppendInt(mHSTSExpireTime);
258 0 : aString.Append(',');
259 0 : aString.AppendInt(mHSTSState);
260 0 : aString.Append(',');
261 0 : aString.AppendInt(static_cast<uint32_t>(mHSTSIncludeSubdomains));
262 0 : aString.Append(',');
263 0 : aString.AppendInt(mHSTSSource);
264 0 : }
265 :
266 : NS_IMETHODIMP
267 0 : SiteHSTSState::GetHostname(nsACString& aHostname)
268 : {
269 0 : aHostname = mHostname;
270 0 : return NS_OK;
271 : }
272 :
273 : NS_IMETHODIMP
274 0 : SiteHSTSState::GetExpireTime(int64_t* aExpireTime)
275 : {
276 0 : NS_ENSURE_ARG(aExpireTime);
277 0 : *aExpireTime = mHSTSExpireTime;
278 0 : return NS_OK;
279 : }
280 :
281 : NS_IMETHODIMP
282 0 : SiteHSTSState::GetSecurityPropertyState(int16_t* aSecurityPropertyState)
283 : {
284 0 : NS_ENSURE_ARG(aSecurityPropertyState);
285 0 : *aSecurityPropertyState = mHSTSState;
286 0 : return NS_OK;
287 : }
288 :
289 : NS_IMETHODIMP
290 0 : SiteHSTSState::GetIncludeSubdomains(bool* aIncludeSubdomains)
291 : {
292 0 : NS_ENSURE_ARG(aIncludeSubdomains);
293 0 : *aIncludeSubdomains = mHSTSIncludeSubdomains;
294 0 : return NS_OK;
295 : }
296 :
297 : NS_IMETHODIMP
298 0 : SiteHSTSState::GetOriginAttributes(JSContext* aCx,
299 : JS::MutableHandle<JS::Value> aOriginAttributes)
300 : {
301 0 : if (!ToJSValue(aCx, mOriginAttributes, aOriginAttributes)) {
302 0 : return NS_ERROR_FAILURE;
303 : }
304 0 : return NS_OK;
305 : }
306 :
307 : ////////////////////////////////////////////////////////////////////////////////
308 :
309 0 : NS_IMPL_ISUPPORTS(SiteHPKPState, nsISiteSecurityState, nsISiteHPKPState)
310 :
311 : namespace {
312 :
313 : // Parses a state string like
314 : // "1494603034103,1,1,AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" into its
315 : // constituent parts.
316 : bool
317 0 : ParseHPKPState(const nsCString& stateString,
318 : /*out*/ PRTime& expireTime,
319 : /*out*/ SecurityPropertyState& state,
320 : /*out*/ bool& includeSubdomains,
321 : /*out*/ nsTArray<nsCString>& sha256keys)
322 : {
323 0 : SSSTokenizer tokenizer(stateString);
324 :
325 0 : if (!tokenizer.ReadInteger(&expireTime)) {
326 0 : return false;
327 : }
328 :
329 0 : if (!tokenizer.CheckChar(',')) {
330 0 : return false;
331 : }
332 :
333 0 : if (!tokenizer.ReadState(state)) {
334 0 : return false;
335 : }
336 :
337 : // SecurityPropertyNegative isn't a valid state for HPKP.
338 0 : switch (state) {
339 : case SecurityPropertyKnockout:
340 : case SecurityPropertySet:
341 : case SecurityPropertyUnset:
342 0 : break;
343 : case SecurityPropertyNegative:
344 : default:
345 0 : return false;
346 : }
347 :
348 0 : if (!tokenizer.CheckChar(',')) {
349 0 : return false;
350 : }
351 :
352 0 : if (!tokenizer.ReadBool(includeSubdomains)) {
353 0 : return false;
354 : }
355 :
356 0 : if (!tokenizer.CheckChar(',')) {
357 0 : return false;
358 : }
359 :
360 0 : if (state == SecurityPropertySet) {
361 : // This reads to the end of input, so there's no need to explicitly check
362 : // for EOF.
363 0 : return tokenizer.ReadUntilEOFAsSHA256Keys(sha256keys);
364 : }
365 :
366 0 : return tokenizer.CheckEOF();
367 : }
368 :
369 : } // namespace
370 :
371 0 : SiteHPKPState::SiteHPKPState()
372 : : mExpireTime(0)
373 : , mState(SecurityPropertyUnset)
374 0 : , mIncludeSubdomains(false)
375 : {
376 0 : }
377 :
378 0 : SiteHPKPState::SiteHPKPState(const nsCString& aHost,
379 : const OriginAttributes& aOriginAttributes,
380 0 : const nsCString& aStateString)
381 : : mHostname(aHost)
382 : , mOriginAttributes(aOriginAttributes)
383 : , mExpireTime(0)
384 : , mState(SecurityPropertyUnset)
385 0 : , mIncludeSubdomains(false)
386 : {
387 0 : bool valid = ParseHPKPState(aStateString, mExpireTime, mState,
388 0 : mIncludeSubdomains, mSHA256keys);
389 0 : if (!valid) {
390 0 : SSSLOG(("%s is not a valid SiteHPKPState", aStateString.get()));
391 0 : mExpireTime = 0;
392 0 : mState = SecurityPropertyUnset;
393 0 : mIncludeSubdomains = false;
394 0 : if (!mSHA256keys.IsEmpty()) {
395 0 : mSHA256keys.Clear();
396 : }
397 : }
398 0 : }
399 :
400 0 : SiteHPKPState::SiteHPKPState(const nsCString& aHost,
401 : const OriginAttributes& aOriginAttributes,
402 : PRTime aExpireTime,
403 : SecurityPropertyState aState,
404 : bool aIncludeSubdomains,
405 0 : nsTArray<nsCString>& aSHA256keys)
406 : : mHostname(aHost)
407 : , mOriginAttributes(aOriginAttributes)
408 : , mExpireTime(aExpireTime)
409 : , mState(aState)
410 : , mIncludeSubdomains(aIncludeSubdomains)
411 0 : , mSHA256keys(aSHA256keys)
412 : {
413 0 : }
414 :
415 : NS_IMETHODIMP
416 0 : SiteHPKPState::GetHostname(nsACString& aHostname)
417 : {
418 0 : aHostname = mHostname;
419 0 : return NS_OK;
420 : }
421 :
422 : NS_IMETHODIMP
423 0 : SiteHPKPState::GetExpireTime(int64_t* aExpireTime)
424 : {
425 0 : NS_ENSURE_ARG(aExpireTime);
426 0 : *aExpireTime = mExpireTime;
427 0 : return NS_OK;
428 : }
429 :
430 : NS_IMETHODIMP
431 0 : SiteHPKPState::GetSecurityPropertyState(int16_t* aSecurityPropertyState)
432 : {
433 0 : NS_ENSURE_ARG(aSecurityPropertyState);
434 0 : *aSecurityPropertyState = mState;
435 0 : return NS_OK;
436 : }
437 :
438 : NS_IMETHODIMP
439 0 : SiteHPKPState::GetIncludeSubdomains(bool* aIncludeSubdomains)
440 : {
441 0 : NS_ENSURE_ARG(aIncludeSubdomains);
442 0 : *aIncludeSubdomains = mIncludeSubdomains;
443 0 : return NS_OK;
444 : }
445 :
446 : void
447 0 : SiteHPKPState::ToString(nsCString& aString)
448 : {
449 0 : aString.Truncate();
450 0 : aString.AppendInt(mExpireTime);
451 0 : aString.Append(',');
452 0 : aString.AppendInt(mState);
453 0 : aString.Append(',');
454 0 : aString.AppendInt(static_cast<uint32_t>(mIncludeSubdomains));
455 0 : aString.Append(',');
456 0 : for (unsigned int i = 0; i < mSHA256keys.Length(); i++) {
457 0 : aString.Append(mSHA256keys[i]);
458 : }
459 0 : }
460 :
461 : NS_IMETHODIMP
462 0 : SiteHPKPState::GetSha256Keys(nsISimpleEnumerator** aSha256Keys)
463 : {
464 0 : NS_ENSURE_ARG(aSha256Keys);
465 :
466 0 : nsCOMArray<nsIVariant> keys;
467 0 : for (const nsCString& key : mSHA256keys) {
468 0 : nsCOMPtr<nsIWritableVariant> variant = new nsVariant();
469 0 : nsresult rv = variant->SetAsAUTF8String(key);
470 0 : if (NS_FAILED(rv)) {
471 0 : return rv;
472 : }
473 0 : if (!keys.AppendObject(variant)) {
474 0 : return NS_ERROR_FAILURE;
475 : }
476 : }
477 0 : return NS_NewArrayEnumerator(aSha256Keys, keys);
478 : }
479 :
480 : NS_IMETHODIMP
481 0 : SiteHPKPState::GetOriginAttributes(JSContext* aCx,
482 : JS::MutableHandle<JS::Value> aOriginAttributes)
483 : {
484 0 : if (!ToJSValue(aCx, mOriginAttributes, aOriginAttributes)) {
485 0 : return NS_ERROR_FAILURE;
486 : }
487 0 : return NS_OK;
488 : }
489 :
490 : ////////////////////////////////////////////////////////////////////////////////
491 :
492 : const uint64_t kSixtyDaysInSeconds = 60 * 24 * 60 * 60;
493 :
494 2 : nsSiteSecurityService::nsSiteSecurityService()
495 : : mMaxMaxAge(kSixtyDaysInSeconds)
496 : , mUsePreloadList(true)
497 2 : , mPreloadListTimeOffset(0)
498 : {
499 2 : }
500 :
501 0 : nsSiteSecurityService::~nsSiteSecurityService()
502 : {
503 0 : }
504 :
505 78 : NS_IMPL_ISUPPORTS(nsSiteSecurityService,
506 : nsIObserver,
507 : nsISiteSecurityService)
508 :
509 : nsresult
510 2 : nsSiteSecurityService::Init()
511 : {
512 : // Don't access Preferences off the main thread.
513 2 : if (!NS_IsMainThread()) {
514 0 : MOZ_ASSERT_UNREACHABLE("nsSiteSecurityService initialized off main thread");
515 : return NS_ERROR_NOT_SAME_THREAD;
516 : }
517 :
518 2 : mMaxMaxAge = mozilla::Preferences::GetInt(
519 : "security.cert_pinning.max_max_age_seconds", kSixtyDaysInSeconds);
520 2 : mozilla::Preferences::AddStrongObserver(this,
521 2 : "security.cert_pinning.max_max_age_seconds");
522 2 : mUsePreloadList = mozilla::Preferences::GetBool(
523 : "network.stricttransportsecurity.preloadlist", true);
524 2 : mozilla::Preferences::AddStrongObserver(this,
525 2 : "network.stricttransportsecurity.preloadlist");
526 2 : mProcessPKPHeadersFromNonBuiltInRoots = mozilla::Preferences::GetBool(
527 : "security.cert_pinning.process_headers_from_non_builtin_roots", false);
528 2 : mozilla::Preferences::AddStrongObserver(this,
529 2 : "security.cert_pinning.process_headers_from_non_builtin_roots");
530 2 : mPreloadListTimeOffset = mozilla::Preferences::GetInt(
531 : "test.currentTimeOffsetSeconds", 0);
532 2 : mozilla::Preferences::AddStrongObserver(this,
533 2 : "test.currentTimeOffsetSeconds");
534 : mSiteStateStorage =
535 2 : mozilla::DataStorage::Get(DataStorageClass::SiteSecurityServiceState);
536 : mPreloadStateStorage =
537 2 : mozilla::DataStorage::Get(DataStorageClass::SecurityPreloadState);
538 2 : bool storageWillPersist = false;
539 2 : bool preloadStorageWillPersist = false;
540 2 : nsresult rv = mSiteStateStorage->Init(storageWillPersist);
541 2 : if (NS_WARN_IF(NS_FAILED(rv))) {
542 0 : return rv;
543 : }
544 2 : rv = mPreloadStateStorage->Init(preloadStorageWillPersist);
545 2 : if (NS_WARN_IF(NS_FAILED(rv))) {
546 0 : return rv;
547 : }
548 : // This is not fatal. There are some cases where there won't be a
549 : // profile directory (e.g. running xpcshell). There isn't the
550 : // expectation that site information will be presisted in those cases.
551 2 : if (!storageWillPersist || !preloadStorageWillPersist) {
552 1 : NS_WARNING("site security information will not be persisted");
553 : }
554 :
555 2 : return NS_OK;
556 : }
557 :
558 : nsresult
559 9 : nsSiteSecurityService::GetHost(nsIURI* aURI, nsACString& aResult)
560 : {
561 18 : nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
562 9 : if (!innerURI) {
563 0 : return NS_ERROR_FAILURE;
564 : }
565 :
566 18 : nsAutoCString host;
567 9 : nsresult rv = innerURI->GetAsciiHost(host);
568 9 : if (NS_FAILED(rv)) {
569 0 : return rv;
570 : }
571 :
572 9 : aResult.Assign(PublicKeyPinningService::CanonicalizeHostname(host.get()));
573 9 : if (aResult.IsEmpty()) {
574 0 : return NS_ERROR_UNEXPECTED;
575 : }
576 :
577 9 : return NS_OK;
578 : }
579 :
580 : static void
581 16 : SetStorageKey(const nsACString& hostname, uint32_t aType,
582 : const OriginAttributes& aOriginAttributes,
583 : /*out*/ nsAutoCString& storageKey)
584 : {
585 16 : storageKey = hostname;
586 :
587 : // Don't isolate by userContextId.
588 32 : OriginAttributes originAttributesNoUserContext = aOriginAttributes;
589 16 : originAttributesNoUserContext.mUserContextId =
590 : nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID;
591 32 : nsAutoCString originAttributesSuffix;
592 16 : originAttributesNoUserContext.CreateSuffix(originAttributesSuffix);
593 16 : storageKey.Append(originAttributesSuffix);
594 16 : switch (aType) {
595 : case nsISiteSecurityService::HEADER_HSTS:
596 16 : storageKey.AppendASCII(kHSTSKeySuffix);
597 16 : break;
598 : case nsISiteSecurityService::HEADER_HPKP:
599 0 : storageKey.AppendASCII(kHPKPKeySuffix);
600 0 : break;
601 : default:
602 0 : MOZ_ASSERT_UNREACHABLE("SSS:SetStorageKey got invalid type");
603 : }
604 16 : }
605 :
606 : // Expire times are in millis. Since Headers max-age is in seconds, and
607 : // PR_Now() is in micros, normalize the units at milliseconds.
608 : static int64_t
609 0 : ExpireTimeFromMaxAge(uint64_t maxAge)
610 : {
611 0 : return (PR_Now() / PR_USEC_PER_MSEC) + ((int64_t)maxAge * PR_MSEC_PER_SEC);
612 : }
613 :
614 : nsresult
615 0 : nsSiteSecurityService::SetHSTSState(uint32_t aType,
616 : const char* aHost,
617 : int64_t maxage,
618 : bool includeSubdomains,
619 : uint32_t flags,
620 : SecurityPropertyState aHSTSState,
621 : SecurityPropertySource aSource,
622 : const OriginAttributes& aOriginAttributes)
623 : {
624 0 : nsAutoCString hostname(aHost);
625 0 : bool isPreload = (aSource == SourcePreload);
626 : // If max-age is zero, that's an indication to immediately remove the
627 : // security state, so here's a shortcut.
628 0 : if (!maxage) {
629 0 : return RemoveStateInternal(aType, hostname, flags, isPreload,
630 0 : aOriginAttributes);
631 : }
632 :
633 0 : MOZ_ASSERT((aHSTSState == SecurityPropertySet ||
634 : aHSTSState == SecurityPropertyNegative),
635 : "HSTS State must be SecurityPropertySet or SecurityPropertyNegative");
636 0 : if (isPreload && aOriginAttributes != OriginAttributes()) {
637 0 : return NS_ERROR_INVALID_ARG;
638 : }
639 :
640 0 : int64_t expiretime = ExpireTimeFromMaxAge(maxage);
641 : RefPtr<SiteHSTSState> siteState = new SiteHSTSState(
642 : hostname, aOriginAttributes, expiretime, aHSTSState, includeSubdomains,
643 0 : aSource);
644 0 : nsAutoCString stateString;
645 0 : siteState->ToString(stateString);
646 0 : SSSLOG(("SSS: setting state for %s", hostname.get()));
647 0 : bool isPrivate = flags & nsISocketProvider::NO_PERMANENT_STORAGE;
648 : mozilla::DataStorageType storageType = isPrivate
649 0 : ? mozilla::DataStorage_Private
650 0 : : mozilla::DataStorage_Persistent;
651 0 : nsAutoCString storageKey;
652 0 : SetStorageKey(hostname, aType, aOriginAttributes, storageKey);
653 : nsresult rv;
654 0 : if (isPreload) {
655 0 : SSSLOG(("SSS: storing entry for %s in dynamic preloads", hostname.get()));
656 0 : rv = mPreloadStateStorage->Put(storageKey, stateString,
657 0 : mozilla::DataStorage_Persistent);
658 : } else {
659 0 : SSSLOG(("SSS: storing HSTS site entry for %s", hostname.get()));
660 0 : nsCString value = mSiteStateStorage->Get(storageKey, storageType);
661 : RefPtr<SiteHSTSState> curSiteState =
662 0 : new SiteHSTSState(hostname, aOriginAttributes, value);
663 0 : if (curSiteState->mHSTSState != SecurityPropertyUnset &&
664 0 : curSiteState->mHSTSSource != SourceUnknown) {
665 : // don't override the source
666 0 : siteState->mHSTSSource = curSiteState->mHSTSSource;
667 0 : siteState->ToString(stateString);
668 : }
669 0 : rv = mSiteStateStorage->Put(storageKey, stateString, storageType);
670 : }
671 0 : NS_ENSURE_SUCCESS(rv, rv);
672 :
673 0 : return NS_OK;
674 : }
675 :
676 : NS_IMETHODIMP
677 0 : nsSiteSecurityService::CacheNegativeHSTSResult(
678 : nsIURI* aSourceURI,
679 : uint64_t aMaxAge,
680 : const OriginAttributes& aOriginAttributes)
681 : {
682 0 : nsAutoCString hostname;
683 0 : nsresult rv = GetHost(aSourceURI, hostname);
684 0 : NS_ENSURE_SUCCESS(rv, rv);
685 : // SecurityPropertyNegative results only come from HSTS priming
686 0 : return SetHSTSState(nsISiteSecurityService::HEADER_HSTS, hostname.get(),
687 : aMaxAge, false, 0, SecurityPropertyNegative,
688 0 : SourceHSTSPriming, aOriginAttributes);
689 : }
690 :
691 : nsresult
692 0 : nsSiteSecurityService::RemoveStateInternal(
693 : uint32_t aType, nsIURI* aURI, uint32_t aFlags,
694 : const OriginAttributes& aOriginAttributes)
695 : {
696 0 : nsAutoCString hostname;
697 0 : GetHost(aURI, hostname);
698 0 : return RemoveStateInternal(aType, hostname, aFlags, false, aOriginAttributes);
699 : }
700 :
701 : nsresult
702 0 : nsSiteSecurityService::RemoveStateInternal(
703 : uint32_t aType,
704 : const nsAutoCString& aHost,
705 : uint32_t aFlags, bool aIsPreload,
706 : const OriginAttributes& aOriginAttributes)
707 : {
708 : // Child processes are not allowed direct access to this.
709 0 : if (!XRE_IsParentProcess()) {
710 0 : MOZ_CRASH("Child process: no direct access to nsISiteSecurityService::RemoveStateInternal");
711 : }
712 :
713 : // Only HSTS is supported at the moment.
714 0 : NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS ||
715 : aType == nsISiteSecurityService::HEADER_HPKP,
716 : NS_ERROR_NOT_IMPLEMENTED);
717 0 : if (aIsPreload && aOriginAttributes != OriginAttributes()) {
718 0 : return NS_ERROR_INVALID_ARG;
719 : }
720 :
721 0 : bool isPrivate = aFlags & nsISocketProvider::NO_PERMANENT_STORAGE;
722 : mozilla::DataStorageType storageType = isPrivate
723 0 : ? mozilla::DataStorage_Private
724 0 : : mozilla::DataStorage_Persistent;
725 : // If this host is in the preload list, we have to store a knockout entry.
726 0 : nsAutoCString storageKey;
727 0 : SetStorageKey(aHost, aType, aOriginAttributes, storageKey);
728 :
729 : nsCString value = mPreloadStateStorage->Get(storageKey,
730 0 : mozilla::DataStorage_Persistent);
731 : RefPtr<SiteHSTSState> dynamicState =
732 0 : new SiteHSTSState(aHost, aOriginAttributes, value);
733 0 : if (GetPreloadListEntry(aHost.get()) ||
734 0 : dynamicState->mHSTSState != SecurityPropertyUnset) {
735 0 : SSSLOG(("SSS: storing knockout entry for %s", aHost.get()));
736 : RefPtr<SiteHSTSState> siteState = new SiteHSTSState(
737 : aHost, aOriginAttributes, 0, SecurityPropertyKnockout, false,
738 0 : SourceUnknown);
739 0 : nsAutoCString stateString;
740 0 : siteState->ToString(stateString);
741 : nsresult rv;
742 0 : if (aIsPreload) {
743 0 : rv = mPreloadStateStorage->Put(storageKey, stateString,
744 0 : mozilla::DataStorage_Persistent);
745 : } else {
746 0 : rv = mSiteStateStorage->Put(storageKey, stateString, storageType);
747 : }
748 0 : NS_ENSURE_SUCCESS(rv, rv);
749 : } else {
750 0 : SSSLOG(("SSS: removing entry for %s", aHost.get()));
751 0 : if (aIsPreload) {
752 0 : mPreloadStateStorage->Remove(storageKey, mozilla::DataStorage_Persistent);
753 : } else {
754 0 : mSiteStateStorage->Remove(storageKey, storageType);
755 : }
756 : }
757 :
758 0 : return NS_OK;
759 : }
760 :
761 : NS_IMETHODIMP
762 0 : nsSiteSecurityService::RemoveState(uint32_t aType, nsIURI* aURI,
763 : uint32_t aFlags,
764 : JS::HandleValue aOriginAttributes,
765 : JSContext* aCx, uint8_t aArgc)
766 : {
767 0 : OriginAttributes originAttributes;
768 0 : if (aArgc > 0) {
769 : // OriginAttributes were passed in.
770 0 : if (!aOriginAttributes.isObject() ||
771 0 : !originAttributes.Init(aCx, aOriginAttributes)) {
772 0 : return NS_ERROR_INVALID_ARG;
773 : }
774 : }
775 0 : return RemoveStateInternal(aType, aURI, aFlags, originAttributes);
776 : }
777 :
778 : static bool
779 17 : HostIsIPAddress(const nsCString& hostname)
780 : {
781 : PRNetAddr hostAddr;
782 17 : PRErrorCode prv = PR_StringToNetAddr(hostname.get(), &hostAddr);
783 17 : return (prv == PR_SUCCESS);
784 : }
785 :
786 : NS_IMETHODIMP
787 0 : nsSiteSecurityService::ProcessHeaderScriptable(
788 : uint32_t aType,
789 : nsIURI* aSourceURI,
790 : const nsACString& aHeader,
791 : nsISSLStatus* aSSLStatus,
792 : uint32_t aFlags,
793 : uint32_t aSource,
794 : JS::HandleValue aOriginAttributes,
795 : uint64_t* aMaxAge,
796 : bool* aIncludeSubdomains,
797 : uint32_t* aFailureResult,
798 : JSContext* aCx,
799 : uint8_t aArgc)
800 : {
801 0 : OriginAttributes originAttributes;
802 0 : if (aArgc > 0) {
803 0 : if (!aOriginAttributes.isObject() ||
804 0 : !originAttributes.Init(aCx, aOriginAttributes)) {
805 0 : return NS_ERROR_INVALID_ARG;
806 : }
807 : }
808 : return ProcessHeader(aType, aSourceURI, aHeader, aSSLStatus, aFlags,
809 : aSource, originAttributes, aMaxAge, aIncludeSubdomains,
810 0 : aFailureResult);
811 : }
812 :
813 : NS_IMETHODIMP
814 0 : nsSiteSecurityService::ProcessHeader(uint32_t aType,
815 : nsIURI* aSourceURI,
816 : const nsACString& aHeader,
817 : nsISSLStatus* aSSLStatus,
818 : uint32_t aFlags,
819 : uint32_t aHeaderSource,
820 : const OriginAttributes& aOriginAttributes,
821 : uint64_t* aMaxAge,
822 : bool* aIncludeSubdomains,
823 : uint32_t* aFailureResult)
824 : {
825 : // Child processes are not allowed direct access to this.
826 0 : if (!XRE_IsParentProcess()) {
827 0 : MOZ_CRASH("Child process: no direct access to "
828 : "nsISiteSecurityService::ProcessHeader");
829 : }
830 :
831 0 : if (aFailureResult) {
832 0 : *aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN;
833 : }
834 0 : NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS ||
835 : aType == nsISiteSecurityService::HEADER_HPKP,
836 : NS_ERROR_NOT_IMPLEMENTED);
837 0 : SecurityPropertySource source = static_cast<SecurityPropertySource>(aHeaderSource);
838 0 : switch (source) {
839 : case SourceUnknown:
840 : case SourcePreload:
841 : case SourceOrganic:
842 : case SourceHSTSPriming:
843 0 : break;
844 : default:
845 0 : return NS_ERROR_INVALID_ARG;
846 : }
847 :
848 0 : NS_ENSURE_ARG(aSSLStatus);
849 0 : return ProcessHeaderInternal(aType, aSourceURI, PromiseFlatCString(aHeader),
850 : aSSLStatus, aFlags, source, aOriginAttributes,
851 0 : aMaxAge, aIncludeSubdomains, aFailureResult);
852 : }
853 :
854 : nsresult
855 0 : nsSiteSecurityService::ProcessHeaderInternal(
856 : uint32_t aType,
857 : nsIURI* aSourceURI,
858 : const nsCString& aHeader,
859 : nsISSLStatus* aSSLStatus,
860 : uint32_t aFlags,
861 : SecurityPropertySource aSource,
862 : const OriginAttributes& aOriginAttributes,
863 : uint64_t* aMaxAge,
864 : bool* aIncludeSubdomains,
865 : uint32_t* aFailureResult)
866 : {
867 0 : if (aFailureResult) {
868 0 : *aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN;
869 : }
870 : // Only HSTS and HPKP are supported at the moment.
871 0 : NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS ||
872 : aType == nsISiteSecurityService::HEADER_HPKP,
873 : NS_ERROR_NOT_IMPLEMENTED);
874 :
875 0 : if (aMaxAge != nullptr) {
876 0 : *aMaxAge = 0;
877 : }
878 :
879 0 : if (aIncludeSubdomains != nullptr) {
880 0 : *aIncludeSubdomains = false;
881 : }
882 :
883 0 : if (aSSLStatus) {
884 0 : bool tlsIsBroken = false;
885 : bool trustcheck;
886 : nsresult rv;
887 0 : rv = aSSLStatus->GetIsDomainMismatch(&trustcheck);
888 0 : NS_ENSURE_SUCCESS(rv, rv);
889 0 : tlsIsBroken = tlsIsBroken || trustcheck;
890 :
891 0 : rv = aSSLStatus->GetIsNotValidAtThisTime(&trustcheck);
892 0 : NS_ENSURE_SUCCESS(rv, rv);
893 0 : tlsIsBroken = tlsIsBroken || trustcheck;
894 :
895 0 : rv = aSSLStatus->GetIsUntrusted(&trustcheck);
896 0 : NS_ENSURE_SUCCESS(rv, rv);
897 0 : tlsIsBroken = tlsIsBroken || trustcheck;
898 0 : if (tlsIsBroken) {
899 0 : SSSLOG(("SSS: discarding header from untrustworthy connection"));
900 0 : if (aFailureResult) {
901 0 : *aFailureResult = nsISiteSecurityService::ERROR_UNTRUSTWORTHY_CONNECTION;
902 : }
903 0 : return NS_ERROR_FAILURE;
904 : }
905 : }
906 :
907 0 : nsAutoCString host;
908 0 : nsresult rv = GetHost(aSourceURI, host);
909 0 : NS_ENSURE_SUCCESS(rv, rv);
910 0 : if (HostIsIPAddress(host)) {
911 : /* Don't process headers if a site is accessed by IP address. */
912 0 : return NS_OK;
913 : }
914 :
915 0 : switch (aType) {
916 : case nsISiteSecurityService::HEADER_HSTS:
917 : rv = ProcessSTSHeader(aSourceURI, aHeader, aFlags, aSource,
918 : aOriginAttributes, aMaxAge, aIncludeSubdomains,
919 0 : aFailureResult);
920 0 : break;
921 : case nsISiteSecurityService::HEADER_HPKP:
922 : rv = ProcessPKPHeader(aSourceURI, aHeader, aSSLStatus, aFlags,
923 : aOriginAttributes, aMaxAge, aIncludeSubdomains,
924 0 : aFailureResult);
925 0 : break;
926 : default:
927 0 : MOZ_CRASH("unexpected header type");
928 : }
929 0 : return rv;
930 : }
931 :
932 : static uint32_t
933 0 : ParseSSSHeaders(uint32_t aType,
934 : const nsCString& aHeader,
935 : bool& foundIncludeSubdomains,
936 : bool& foundMaxAge,
937 : bool& foundUnrecognizedDirective,
938 : uint64_t& maxAge,
939 : nsTArray<nsCString>& sha256keys)
940 : {
941 : // Strict transport security and Public Key Pinning have very similar
942 : // Header formats.
943 :
944 : // "Strict-Transport-Security" ":" OWS
945 : // STS-d *( OWS ";" OWS STS-d OWS)
946 : //
947 : // ; STS directive
948 : // STS-d = maxAge / includeSubDomains
949 : //
950 : // maxAge = "max-age" "=" delta-seconds v-ext
951 : //
952 : // includeSubDomains = [ "includeSubDomains" ]
953 : //
954 :
955 : // "Public-Key-Pins ":" OWS
956 : // PKP-d *( OWS ";" OWS PKP-d OWS)
957 : //
958 : // ; PKP directive
959 : // PKP-d = maxAge / includeSubDomains / reportUri / pin-directive
960 : //
961 : // maxAge = "max-age" "=" delta-seconds v-ext
962 : //
963 : // includeSubDomains = [ "includeSubDomains" ]
964 : //
965 : // reportURi = "report-uri" "=" quoted-string
966 : //
967 : // pin-directive = "pin-" token "=" quoted-string
968 : //
969 : // the only valid token currently specified is sha256
970 : // the quoted string for a pin directive is the base64 encoding
971 : // of the hash of the public key of the fingerprint
972 : //
973 :
974 : // The order of the directives is not significant.
975 : // All directives must appear only once.
976 : // Directive names are case-insensitive.
977 : // The entire header is invalid if a directive not conforming to the
978 : // syntax is encountered.
979 : // Unrecognized directives (that are otherwise syntactically valid) are
980 : // ignored, and the rest of the header is parsed as normal.
981 :
982 0 : bool foundReportURI = false;
983 :
984 0 : NS_NAMED_LITERAL_CSTRING(max_age_var, "max-age");
985 0 : NS_NAMED_LITERAL_CSTRING(include_subd_var, "includesubdomains");
986 0 : NS_NAMED_LITERAL_CSTRING(pin_sha256_var, "pin-sha256");
987 0 : NS_NAMED_LITERAL_CSTRING(report_uri_var, "report-uri");
988 :
989 0 : nsSecurityHeaderParser parser(aHeader);
990 0 : nsresult rv = parser.Parse();
991 0 : if (NS_FAILED(rv)) {
992 0 : SSSLOG(("SSS: could not parse header"));
993 0 : return nsISiteSecurityService::ERROR_COULD_NOT_PARSE_HEADER;
994 : }
995 0 : mozilla::LinkedList<nsSecurityHeaderDirective>* directives = parser.GetDirectives();
996 :
997 0 : for (nsSecurityHeaderDirective* directive = directives->getFirst();
998 0 : directive != nullptr; directive = directive->getNext()) {
999 0 : SSSLOG(("SSS: found directive %s\n", directive->mName.get()));
1000 0 : if (directive->mName.Length() == max_age_var.Length() &&
1001 0 : directive->mName.EqualsIgnoreCase(max_age_var.get(),
1002 0 : max_age_var.Length())) {
1003 0 : if (foundMaxAge) {
1004 0 : SSSLOG(("SSS: found two max-age directives"));
1005 0 : return nsISiteSecurityService::ERROR_MULTIPLE_MAX_AGES;
1006 : }
1007 :
1008 0 : SSSLOG(("SSS: found max-age directive"));
1009 0 : foundMaxAge = true;
1010 :
1011 0 : Tokenizer tokenizer(directive->mValue);
1012 0 : if (!tokenizer.ReadInteger(&maxAge)) {
1013 0 : SSSLOG(("SSS: could not parse delta-seconds"));
1014 0 : return nsISiteSecurityService::ERROR_INVALID_MAX_AGE;
1015 : }
1016 :
1017 0 : if (!tokenizer.CheckEOF()) {
1018 0 : SSSLOG(("SSS: invalid value for max-age directive"));
1019 0 : return nsISiteSecurityService::ERROR_INVALID_MAX_AGE;
1020 : }
1021 :
1022 0 : SSSLOG(("SSS: parsed delta-seconds: %" PRIu64, maxAge));
1023 0 : } else if (directive->mName.Length() == include_subd_var.Length() &&
1024 0 : directive->mName.EqualsIgnoreCase(include_subd_var.get(),
1025 0 : include_subd_var.Length())) {
1026 0 : if (foundIncludeSubdomains) {
1027 0 : SSSLOG(("SSS: found two includeSubdomains directives"));
1028 0 : return nsISiteSecurityService::ERROR_MULTIPLE_INCLUDE_SUBDOMAINS;
1029 : }
1030 :
1031 0 : SSSLOG(("SSS: found includeSubdomains directive"));
1032 0 : foundIncludeSubdomains = true;
1033 :
1034 0 : if (directive->mValue.Length() != 0) {
1035 0 : SSSLOG(("SSS: includeSubdomains directive unexpectedly had value '%s'",
1036 : directive->mValue.get()));
1037 0 : return nsISiteSecurityService::ERROR_INVALID_INCLUDE_SUBDOMAINS;
1038 : }
1039 0 : } else if (aType == nsISiteSecurityService::HEADER_HPKP &&
1040 0 : directive->mName.Length() == pin_sha256_var.Length() &&
1041 0 : directive->mName.EqualsIgnoreCase(pin_sha256_var.get(),
1042 0 : pin_sha256_var.Length())) {
1043 0 : SSSLOG(("SSS: found pinning entry '%s' length=%d",
1044 : directive->mValue.get(), directive->mValue.Length()));
1045 0 : if (!stringIsBase64EncodingOf256bitValue(directive->mValue)) {
1046 0 : return nsISiteSecurityService::ERROR_INVALID_PIN;
1047 : }
1048 0 : sha256keys.AppendElement(directive->mValue);
1049 0 : } else if (aType == nsISiteSecurityService::HEADER_HPKP &&
1050 0 : directive->mName.Length() == report_uri_var.Length() &&
1051 0 : directive->mName.EqualsIgnoreCase(report_uri_var.get(),
1052 0 : report_uri_var.Length())) {
1053 : // We don't support the report-uri yet, but to avoid unrecognized
1054 : // directive warnings, we still have to handle its presence
1055 0 : if (foundReportURI) {
1056 0 : SSSLOG(("SSS: found two report-uri directives"));
1057 0 : return nsISiteSecurityService::ERROR_MULTIPLE_REPORT_URIS;
1058 : }
1059 0 : SSSLOG(("SSS: found report-uri directive"));
1060 0 : foundReportURI = true;
1061 : } else {
1062 0 : SSSLOG(("SSS: ignoring unrecognized directive '%s'",
1063 : directive->mName.get()));
1064 0 : foundUnrecognizedDirective = true;
1065 : }
1066 : }
1067 0 : return nsISiteSecurityService::Success;
1068 : }
1069 :
1070 : nsresult
1071 0 : nsSiteSecurityService::ProcessPKPHeader(
1072 : nsIURI* aSourceURI,
1073 : const nsCString& aHeader,
1074 : nsISSLStatus* aSSLStatus,
1075 : uint32_t aFlags,
1076 : const OriginAttributes& aOriginAttributes,
1077 : uint64_t* aMaxAge,
1078 : bool* aIncludeSubdomains,
1079 : uint32_t* aFailureResult)
1080 : {
1081 0 : if (aFailureResult) {
1082 0 : *aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN;
1083 : }
1084 0 : SSSLOG(("SSS: processing HPKP header '%s'", aHeader.get()));
1085 0 : NS_ENSURE_ARG(aSSLStatus);
1086 :
1087 0 : const uint32_t aType = nsISiteSecurityService::HEADER_HPKP;
1088 0 : bool foundMaxAge = false;
1089 0 : bool foundIncludeSubdomains = false;
1090 0 : bool foundUnrecognizedDirective = false;
1091 0 : uint64_t maxAge = 0;
1092 0 : nsTArray<nsCString> sha256keys;
1093 : uint32_t sssrv = ParseSSSHeaders(aType, aHeader, foundIncludeSubdomains,
1094 : foundMaxAge, foundUnrecognizedDirective,
1095 0 : maxAge, sha256keys);
1096 0 : if (sssrv != nsISiteSecurityService::Success) {
1097 0 : if (aFailureResult) {
1098 0 : *aFailureResult = sssrv;
1099 : }
1100 0 : return NS_ERROR_FAILURE;
1101 : }
1102 :
1103 : // after processing all the directives, make sure we came across max-age
1104 : // somewhere.
1105 0 : if (!foundMaxAge) {
1106 0 : SSSLOG(("SSS: did not encounter required max-age directive"));
1107 0 : if (aFailureResult) {
1108 0 : *aFailureResult = nsISiteSecurityService::ERROR_NO_MAX_AGE;
1109 : }
1110 0 : return NS_ERROR_FAILURE;
1111 : }
1112 :
1113 : // before we add the pin we need to ensure it will not break the site as
1114 : // currently visited so:
1115 : // 1. recompute a valid chain (no external ocsp)
1116 : // 2. use this chain to check if things would have broken!
1117 0 : nsAutoCString host;
1118 0 : nsresult rv = GetHost(aSourceURI, host);
1119 0 : NS_ENSURE_SUCCESS(rv, rv);
1120 0 : nsCOMPtr<nsIX509Cert> cert;
1121 0 : rv = aSSLStatus->GetServerCert(getter_AddRefs(cert));
1122 0 : NS_ENSURE_SUCCESS(rv, rv);
1123 0 : NS_ENSURE_TRUE(cert, NS_ERROR_FAILURE);
1124 0 : UniqueCERTCertificate nssCert(cert->GetCert());
1125 0 : NS_ENSURE_TRUE(nssCert, NS_ERROR_FAILURE);
1126 :
1127 0 : mozilla::pkix::Time now(mozilla::pkix::Now());
1128 0 : UniqueCERTCertList certList;
1129 0 : RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
1130 0 : NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED);
1131 : // We don't want this verification to cause any network traffic that would
1132 : // block execution. Also, since we don't have access to the original stapled
1133 : // OCSP response, we can't enforce this aspect of the TLS Feature extension.
1134 : // This is ok, because it will have been enforced when we originally connected
1135 : // to the site (or it's disabled, in which case we wouldn't want to enforce it
1136 : // anyway).
1137 0 : CertVerifier::Flags flags = CertVerifier::FLAG_LOCAL_ONLY |
1138 0 : CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
1139 0 : if (certVerifier->VerifySSLServerCert(nssCert,
1140 : nullptr, // stapledOCSPResponse
1141 : nullptr, // sctsFromTLSExtension
1142 : now, nullptr, // pinarg
1143 : host, // hostname
1144 : certList,
1145 : nullptr, // no peerCertChain
1146 : false, // don't store intermediates
1147 : flags,
1148 : aOriginAttributes)
1149 : != mozilla::pkix::Success) {
1150 0 : return NS_ERROR_FAILURE;
1151 : }
1152 :
1153 0 : CERTCertListNode* rootNode = CERT_LIST_TAIL(certList);
1154 0 : if (CERT_LIST_END(rootNode, certList)) {
1155 0 : return NS_ERROR_FAILURE;
1156 : }
1157 0 : bool isBuiltIn = false;
1158 0 : mozilla::pkix::Result result = IsCertBuiltInRoot(rootNode->cert, isBuiltIn);
1159 0 : if (result != mozilla::pkix::Success) {
1160 0 : return NS_ERROR_FAILURE;
1161 : }
1162 :
1163 0 : if (!isBuiltIn && !mProcessPKPHeadersFromNonBuiltInRoots) {
1164 0 : if (aFailureResult) {
1165 0 : *aFailureResult = nsISiteSecurityService::ERROR_ROOT_NOT_BUILT_IN;
1166 : }
1167 0 : return NS_ERROR_FAILURE;
1168 : }
1169 :
1170 : // if maxAge == 0 we must delete all state, for now no hole-punching
1171 0 : if (maxAge == 0) {
1172 0 : return RemoveStateInternal(aType, aSourceURI, aFlags, aOriginAttributes);
1173 : }
1174 :
1175 : // clamp maxAge to the maximum set by pref
1176 0 : if (maxAge > mMaxMaxAge) {
1177 0 : maxAge = mMaxMaxAge;
1178 : }
1179 :
1180 : bool chainMatchesPinset;
1181 : rv = PublicKeyPinningService::ChainMatchesPinset(certList, sha256keys,
1182 0 : chainMatchesPinset);
1183 0 : if (NS_FAILED(rv)) {
1184 0 : return rv;
1185 : }
1186 0 : if (!chainMatchesPinset) {
1187 : // is invalid
1188 0 : SSSLOG(("SSS: Pins provided by %s are invalid no match with certList\n", host.get()));
1189 0 : if (aFailureResult) {
1190 0 : *aFailureResult = nsISiteSecurityService::ERROR_PINSET_DOES_NOT_MATCH_CHAIN;
1191 : }
1192 0 : return NS_ERROR_FAILURE;
1193 : }
1194 :
1195 : // finally we need to ensure that there is a "backup pin" ie. There must be
1196 : // at least one fingerprint hash that does NOT validate against the verified
1197 : // chain (Section 2.5 of the spec)
1198 0 : bool hasBackupPin = false;
1199 0 : for (uint32_t i = 0; i < sha256keys.Length(); i++) {
1200 0 : nsTArray<nsCString> singlePin;
1201 0 : singlePin.AppendElement(sha256keys[i]);
1202 : rv = PublicKeyPinningService::ChainMatchesPinset(certList, singlePin,
1203 0 : chainMatchesPinset);
1204 0 : if (NS_FAILED(rv)) {
1205 0 : return rv;
1206 : }
1207 0 : if (!chainMatchesPinset) {
1208 0 : hasBackupPin = true;
1209 : }
1210 : }
1211 0 : if (!hasBackupPin) {
1212 : // is invalid
1213 0 : SSSLOG(("SSS: Pins provided by %s are invalid no backupPin\n", host.get()));
1214 0 : if (aFailureResult) {
1215 0 : *aFailureResult = nsISiteSecurityService::ERROR_NO_BACKUP_PIN;
1216 : }
1217 0 : return NS_ERROR_FAILURE;
1218 : }
1219 :
1220 0 : int64_t expireTime = ExpireTimeFromMaxAge(maxAge);
1221 : RefPtr<SiteHPKPState> dynamicEntry =
1222 : new SiteHPKPState(host, aOriginAttributes, expireTime, SecurityPropertySet,
1223 0 : foundIncludeSubdomains, sha256keys);
1224 0 : SSSLOG(("SSS: about to set pins for %s, expires=%" PRId64 " now=%" PRId64 " maxAge=%" PRIu64 "\n",
1225 : host.get(), expireTime, PR_Now() / PR_USEC_PER_MSEC, maxAge));
1226 :
1227 0 : rv = SetHPKPState(host.get(), *dynamicEntry, aFlags, false, aOriginAttributes);
1228 0 : if (NS_FAILED(rv)) {
1229 0 : SSSLOG(("SSS: failed to set pins for %s\n", host.get()));
1230 0 : if (aFailureResult) {
1231 0 : *aFailureResult = nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE;
1232 : }
1233 0 : return rv;
1234 : }
1235 :
1236 0 : if (aMaxAge != nullptr) {
1237 0 : *aMaxAge = maxAge;
1238 : }
1239 :
1240 0 : if (aIncludeSubdomains != nullptr) {
1241 0 : *aIncludeSubdomains = foundIncludeSubdomains;
1242 : }
1243 :
1244 : return foundUnrecognizedDirective
1245 0 : ? NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA
1246 0 : : NS_OK;
1247 : }
1248 :
1249 : nsresult
1250 0 : nsSiteSecurityService::ProcessSTSHeader(
1251 : nsIURI* aSourceURI,
1252 : const nsCString& aHeader,
1253 : uint32_t aFlags,
1254 : SecurityPropertySource aSource,
1255 : const OriginAttributes& aOriginAttributes,
1256 : uint64_t* aMaxAge,
1257 : bool* aIncludeSubdomains,
1258 : uint32_t* aFailureResult)
1259 : {
1260 0 : if (aFailureResult) {
1261 0 : *aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN;
1262 : }
1263 0 : SSSLOG(("SSS: processing HSTS header '%s'", aHeader.get()));
1264 :
1265 0 : const uint32_t aType = nsISiteSecurityService::HEADER_HSTS;
1266 0 : bool foundMaxAge = false;
1267 0 : bool foundIncludeSubdomains = false;
1268 0 : bool foundUnrecognizedDirective = false;
1269 0 : uint64_t maxAge = 0;
1270 0 : nsTArray<nsCString> unusedSHA256keys; // Required for sane internal interface
1271 :
1272 : uint32_t sssrv = ParseSSSHeaders(aType, aHeader, foundIncludeSubdomains,
1273 : foundMaxAge, foundUnrecognizedDirective,
1274 0 : maxAge, unusedSHA256keys);
1275 0 : if (sssrv != nsISiteSecurityService::Success) {
1276 0 : if (aFailureResult) {
1277 0 : *aFailureResult = sssrv;
1278 : }
1279 0 : return NS_ERROR_FAILURE;
1280 : }
1281 :
1282 : // after processing all the directives, make sure we came across max-age
1283 : // somewhere.
1284 0 : if (!foundMaxAge) {
1285 0 : SSSLOG(("SSS: did not encounter required max-age directive"));
1286 0 : if (aFailureResult) {
1287 0 : *aFailureResult = nsISiteSecurityService::ERROR_NO_MAX_AGE;
1288 : }
1289 0 : return NS_ERROR_FAILURE;
1290 : }
1291 :
1292 0 : nsAutoCString hostname;
1293 0 : nsresult rv = GetHost(aSourceURI, hostname);
1294 0 : NS_ENSURE_SUCCESS(rv, rv);
1295 :
1296 : // record the successfully parsed header data.
1297 0 : rv = SetHSTSState(aType, hostname.get(), maxAge, foundIncludeSubdomains,
1298 0 : aFlags, SecurityPropertySet, aSource, aOriginAttributes);
1299 0 : if (NS_FAILED(rv)) {
1300 0 : SSSLOG(("SSS: failed to set STS state"));
1301 0 : if (aFailureResult) {
1302 0 : *aFailureResult = nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE;
1303 : }
1304 0 : return rv;
1305 : }
1306 :
1307 0 : if (aMaxAge != nullptr) {
1308 0 : *aMaxAge = maxAge;
1309 : }
1310 :
1311 0 : if (aIncludeSubdomains != nullptr) {
1312 0 : *aIncludeSubdomains = foundIncludeSubdomains;
1313 : }
1314 :
1315 : return foundUnrecognizedDirective
1316 0 : ? NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA
1317 0 : : NS_OK;
1318 : }
1319 :
1320 : NS_IMETHODIMP
1321 0 : nsSiteSecurityService::IsSecureURIScriptable(uint32_t aType, nsIURI* aURI,
1322 : uint32_t aFlags,
1323 : JS::HandleValue aOriginAttributes,
1324 : bool* aCached,
1325 : uint32_t* aSource, JSContext* aCx,
1326 : uint8_t aArgc, bool* aResult)
1327 : {
1328 0 : OriginAttributes originAttributes;
1329 0 : if (aArgc > 0) {
1330 0 : if (!aOriginAttributes.isObject() ||
1331 0 : !originAttributes.Init(aCx, aOriginAttributes)) {
1332 0 : return NS_ERROR_INVALID_ARG;
1333 : }
1334 : }
1335 0 : return IsSecureURI(aType, aURI, aFlags, originAttributes, aCached, aSource, aResult);
1336 : }
1337 :
1338 : NS_IMETHODIMP
1339 9 : nsSiteSecurityService::IsSecureURI(uint32_t aType, nsIURI* aURI,
1340 : uint32_t aFlags,
1341 : const OriginAttributes& aOriginAttributes,
1342 : bool* aCached,
1343 : uint32_t* aSource, bool* aResult)
1344 : {
1345 : // Child processes are not allowed direct access to this.
1346 9 : if (!XRE_IsParentProcess() && aType != nsISiteSecurityService::HEADER_HSTS) {
1347 0 : MOZ_CRASH("Child process: no direct access to nsISiteSecurityService::IsSecureURI for non-HSTS entries");
1348 : }
1349 :
1350 9 : NS_ENSURE_ARG(aURI);
1351 9 : NS_ENSURE_ARG(aResult);
1352 :
1353 : // Only HSTS and HPKP are supported at the moment.
1354 9 : NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS ||
1355 : aType == nsISiteSecurityService::HEADER_HPKP,
1356 : NS_ERROR_NOT_IMPLEMENTED);
1357 :
1358 18 : nsAutoCString hostname;
1359 9 : nsresult rv = GetHost(aURI, hostname);
1360 9 : NS_ENSURE_SUCCESS(rv, rv);
1361 : /* An IP address never qualifies as a secure URI. */
1362 9 : if (HostIsIPAddress(hostname)) {
1363 1 : *aResult = false;
1364 1 : return NS_OK;
1365 : }
1366 :
1367 8 : SecurityPropertySource* source = BitwiseCast<SecurityPropertySource*>(aSource);
1368 :
1369 : return IsSecureHost(aType, hostname, aFlags, aOriginAttributes, aCached,
1370 8 : source, aResult);
1371 : }
1372 :
1373 120 : int STSPreloadCompare(const void *key, const void *entry)
1374 : {
1375 120 : const char *keyStr = (const char *)key;
1376 120 : const nsSTSPreload *preloadEntry = (const nsSTSPreload *)entry;
1377 120 : return strcmp(keyStr, &kSTSHostTable[preloadEntry->mHostIndex]);
1378 : }
1379 :
1380 : // Returns the preload list entry for the given host, if it exists.
1381 : // Only does exact host matching - the user must decide how to use the returned
1382 : // data. May return null.
1383 : const nsSTSPreload *
1384 8 : nsSiteSecurityService::GetPreloadListEntry(const char *aHost)
1385 : {
1386 8 : PRTime currentTime = PR_Now() + (mPreloadListTimeOffset * PR_USEC_PER_SEC);
1387 8 : if (mUsePreloadList && currentTime < gPreloadListExpirationTime) {
1388 8 : return (const nsSTSPreload *) bsearch(aHost,
1389 : kSTSPreloadList,
1390 : mozilla::ArrayLength(kSTSPreloadList),
1391 : sizeof(nsSTSPreload),
1392 8 : STSPreloadCompare);
1393 : }
1394 :
1395 0 : return nullptr;
1396 : }
1397 :
1398 : // Allows us to determine if we have an HSTS entry for a given host (and, if
1399 : // so, what that state is). The return value says whether or not we know
1400 : // anything about this host (true if the host has an HSTS entry). aHost is
1401 : // the host which we wish to deteming HSTS information on,
1402 : // aRequireIncludeSubdomains specifies whether we require includeSubdomains
1403 : // to be set on the entry (with the other parameters being as per IsSecureHost).
1404 : bool
1405 8 : nsSiteSecurityService::HostHasHSTSEntry(
1406 : const nsAutoCString& aHost, bool aRequireIncludeSubdomains, uint32_t aFlags,
1407 : const OriginAttributes& aOriginAttributes, bool* aResult, bool* aCached,
1408 : SecurityPropertySource* aSource)
1409 : {
1410 8 : if (aSource) {
1411 8 : *aSource = SourceUnknown;
1412 : }
1413 8 : if (aCached) {
1414 0 : *aCached = false;
1415 : }
1416 : // First we check for an entry in site security storage. If that entry exists,
1417 : // we don't want to check in the preload lists. We only want to use the
1418 : // stored value if it is not a knockout entry, however.
1419 : // Additionally, if it is a knockout entry, we want to stop looking for data
1420 : // on the host, because the knockout entry indicates "we have no information
1421 : // regarding the security status of this host".
1422 8 : bool isPrivate = aFlags & nsISocketProvider::NO_PERMANENT_STORAGE;
1423 : mozilla::DataStorageType storageType = isPrivate
1424 8 : ? mozilla::DataStorage_Private
1425 8 : : mozilla::DataStorage_Persistent;
1426 16 : nsAutoCString storageKey;
1427 8 : SSSLOG(("Seeking HSTS entry for %s", aHost.get()));
1428 8 : SetStorageKey(aHost, nsISiteSecurityService::HEADER_HSTS, aOriginAttributes,
1429 8 : storageKey);
1430 16 : nsAutoCString preloadKey;
1431 16 : SetStorageKey(aHost, nsISiteSecurityService::HEADER_HSTS, OriginAttributes(),
1432 8 : preloadKey);
1433 16 : nsCString value = mSiteStateStorage->Get(storageKey, storageType);
1434 : RefPtr<SiteHSTSState> siteState =
1435 16 : new SiteHSTSState(aHost, aOriginAttributes, value);
1436 8 : if (siteState->mHSTSState != SecurityPropertyUnset) {
1437 0 : SSSLOG(("Found HSTS entry for %s", aHost.get()));
1438 0 : bool expired = siteState->IsExpired(nsISiteSecurityService::HEADER_HSTS);
1439 0 : if (!expired) {
1440 0 : SSSLOG(("Entry for %s is not expired", aHost.get()));
1441 0 : if (siteState->mHSTSState == SecurityPropertySet) {
1442 0 : *aResult = aRequireIncludeSubdomains ? siteState->mHSTSIncludeSubdomains
1443 : : true;
1444 0 : if (aCached) {
1445 : // Only set cached if this includes subdomains
1446 0 : *aCached = aRequireIncludeSubdomains ? siteState->mHSTSIncludeSubdomains
1447 : : true;
1448 : }
1449 0 : if (aSource) {
1450 0 : *aSource = siteState->mHSTSSource;
1451 : }
1452 0 : return true;
1453 0 : } else if (siteState->mHSTSState == SecurityPropertyNegative) {
1454 0 : *aResult = false;
1455 0 : if (aCached) {
1456 : // if it's negative, it is always cached
1457 0 : SSSLOG(("Marking HSTS as as cached (SecurityPropertyNegative)"));
1458 0 : *aCached = true;
1459 : }
1460 0 : if (aSource) {
1461 0 : *aSource = siteState->mHSTSSource;
1462 : }
1463 0 : return true;
1464 : }
1465 : }
1466 :
1467 0 : if (expired) {
1468 0 : SSSLOG(("Entry %s is expired - checking for preload state", aHost.get()));
1469 : // If the entry is expired and is not in either the static or dynamic
1470 : // preload lists, we can remove it.
1471 : // First, check the dynamic preload list.
1472 0 : value = mPreloadStateStorage->Get(preloadKey,
1473 0 : mozilla::DataStorage_Persistent);
1474 : RefPtr<SiteHSTSState> dynamicState =
1475 0 : new SiteHSTSState(aHost, aOriginAttributes, value);
1476 0 : if (dynamicState->mHSTSState == SecurityPropertyUnset) {
1477 0 : SSSLOG(("No dynamic preload - checking for static preload"));
1478 : // Now check the static preload list.
1479 0 : if (!GetPreloadListEntry(aHost.get())) {
1480 0 : SSSLOG(("No static preload - removing expired entry"));
1481 0 : mSiteStateStorage->Remove(storageKey, storageType);
1482 : }
1483 : }
1484 : }
1485 0 : return false;
1486 : }
1487 :
1488 : // Next, look in the dynamic preload list.
1489 16 : value = mPreloadStateStorage->Get(preloadKey,
1490 8 : mozilla::DataStorage_Persistent);
1491 : RefPtr<SiteHSTSState> dynamicState =
1492 16 : new SiteHSTSState(aHost, aOriginAttributes, value);
1493 8 : if (dynamicState->mHSTSState != SecurityPropertyUnset) {
1494 0 : SSSLOG(("Found dynamic preload entry for %s", aHost.get()));
1495 0 : bool expired = dynamicState->IsExpired(nsISiteSecurityService::HEADER_HSTS);
1496 0 : if (!expired) {
1497 0 : if (dynamicState->mHSTSState == SecurityPropertySet) {
1498 0 : *aResult = aRequireIncludeSubdomains ? dynamicState->mHSTSIncludeSubdomains
1499 : : true;
1500 0 : if (aCached) {
1501 : // Only set cached if this includes subdomains
1502 0 : *aCached = aRequireIncludeSubdomains ? dynamicState->mHSTSIncludeSubdomains
1503 : : true;
1504 : }
1505 0 : if (aSource) {
1506 0 : *aSource = dynamicState->mHSTSSource;
1507 : }
1508 0 : return true;
1509 0 : } else if (dynamicState->mHSTSState == SecurityPropertyNegative) {
1510 0 : *aResult = false;
1511 0 : if (aCached) {
1512 : // if it's negative, it is always cached
1513 0 : *aCached = true;
1514 : }
1515 0 : if (aSource) {
1516 0 : *aSource = dynamicState->mHSTSSource;
1517 : }
1518 0 : return true;
1519 : }
1520 : } else {
1521 : // if a dynamic preload has expired and is not in the static preload
1522 : // list, we can remove it.
1523 0 : if (!GetPreloadListEntry(aHost.get())) {
1524 0 : mPreloadStateStorage->Remove(preloadKey,
1525 0 : mozilla::DataStorage_Persistent);
1526 : }
1527 : }
1528 0 : return false;
1529 : }
1530 :
1531 8 : const nsSTSPreload* preload = nullptr;
1532 :
1533 : // Finally look in the static preload list.
1534 24 : if (siteState->mHSTSState == SecurityPropertyUnset &&
1535 16 : dynamicState->mHSTSState == SecurityPropertyUnset &&
1536 8 : (preload = GetPreloadListEntry(aHost.get())) != nullptr) {
1537 0 : SSSLOG(("%s is a preloaded HSTS host", aHost.get()));
1538 0 : *aResult = aRequireIncludeSubdomains ? preload->mIncludeSubdomains
1539 0 : : true;
1540 0 : if (aCached) {
1541 : // Only set cached if this includes subdomains
1542 0 : *aCached = aRequireIncludeSubdomains ? preload->mIncludeSubdomains
1543 0 : : true;
1544 : }
1545 0 : if (aSource) {
1546 0 : *aSource = SourcePreload;
1547 : }
1548 0 : return true;
1549 : }
1550 :
1551 8 : return false;
1552 : }
1553 :
1554 : nsresult
1555 8 : nsSiteSecurityService::IsSecureHost(uint32_t aType, const nsACString& aHost,
1556 : uint32_t aFlags,
1557 : const OriginAttributes& aOriginAttributes,
1558 : bool* aCached,
1559 : SecurityPropertySource* aSource,
1560 : bool* aResult)
1561 : {
1562 : // Child processes are not allowed direct access to this.
1563 8 : if (!XRE_IsParentProcess() && aType != nsISiteSecurityService::HEADER_HSTS) {
1564 0 : MOZ_CRASH("Child process: no direct access to "
1565 : "nsISiteSecurityService::IsSecureHost for non-HSTS entries");
1566 : }
1567 :
1568 8 : NS_ENSURE_ARG(aResult);
1569 :
1570 : // Only HSTS and HPKP are supported at the moment.
1571 8 : NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS ||
1572 : aType == nsISiteSecurityService::HEADER_HPKP,
1573 : NS_ERROR_NOT_IMPLEMENTED);
1574 :
1575 : // set default in case if we can't find any STS information
1576 8 : *aResult = false;
1577 :
1578 : /* An IP address never qualifies as a secure URI. */
1579 16 : const nsCString& flatHost = PromiseFlatCString(aHost);
1580 8 : if (HostIsIPAddress(flatHost)) {
1581 0 : return NS_OK;
1582 : }
1583 :
1584 8 : if (aType == nsISiteSecurityService::HEADER_HPKP) {
1585 0 : RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
1586 0 : if (!certVerifier) {
1587 0 : return NS_ERROR_FAILURE;
1588 : }
1589 0 : if (certVerifier->mPinningMode ==
1590 : CertVerifier::PinningMode::pinningDisabled) {
1591 0 : return NS_OK;
1592 : }
1593 0 : bool enforceTestMode = certVerifier->mPinningMode ==
1594 0 : CertVerifier::PinningMode::pinningEnforceTestMode;
1595 0 : return PublicKeyPinningService::HostHasPins(flatHost.get(),
1596 : mozilla::pkix::Now(),
1597 : enforceTestMode, aOriginAttributes,
1598 0 : *aResult);
1599 : }
1600 :
1601 : // Holepunch chart.apis.google.com and subdomains.
1602 : nsAutoCString host(
1603 16 : PublicKeyPinningService::CanonicalizeHostname(flatHost.get()));
1604 32 : if (host.EqualsLiteral("chart.apis.google.com") ||
1605 32 : StringEndsWith(host, NS_LITERAL_CSTRING(".chart.apis.google.com"))) {
1606 0 : if (aCached) {
1607 0 : *aCached = true;
1608 : }
1609 0 : if (aSource) {
1610 0 : *aSource = SourcePreload;
1611 : }
1612 0 : return NS_OK;
1613 : }
1614 :
1615 : // First check the exact host.
1616 8 : if (HostHasHSTSEntry(host, false, aFlags, aOriginAttributes, aResult,
1617 : aCached, aSource)) {
1618 0 : return NS_OK;
1619 : }
1620 :
1621 :
1622 8 : SSSLOG(("no HSTS data for %s found, walking up domain", host.get()));
1623 : const char *subdomain;
1624 :
1625 8 : uint32_t offset = 0;
1626 8 : for (offset = host.FindChar('.', offset) + 1;
1627 8 : offset > 0;
1628 0 : offset = host.FindChar('.', offset) + 1) {
1629 :
1630 0 : subdomain = host.get() + offset;
1631 :
1632 : // If we get an empty string, don't continue.
1633 0 : if (strlen(subdomain) < 1) {
1634 0 : break;
1635 : }
1636 :
1637 : // Do the same thing as with the exact host except now we're looking at
1638 : // ancestor domains of the original host and, therefore, we have to require
1639 : // that the entry includes subdomains.
1640 0 : nsAutoCString subdomainString(subdomain);
1641 :
1642 0 : if (HostHasHSTSEntry(subdomainString, true, aFlags, aOriginAttributes, aResult,
1643 : aCached, aSource)) {
1644 0 : break;
1645 : }
1646 :
1647 0 : SSSLOG(("no HSTS data for %s found, walking up domain", subdomain));
1648 : }
1649 :
1650 : // Use whatever we ended up with, which defaults to false.
1651 8 : return NS_OK;
1652 : }
1653 :
1654 : NS_IMETHODIMP
1655 0 : nsSiteSecurityService::ClearAll()
1656 : {
1657 : // Child processes are not allowed direct access to this.
1658 0 : if (!XRE_IsParentProcess()) {
1659 0 : MOZ_CRASH("Child process: no direct access to nsISiteSecurityService::ClearAll");
1660 : }
1661 :
1662 0 : return mSiteStateStorage->Clear();
1663 : }
1664 :
1665 : NS_IMETHODIMP
1666 0 : nsSiteSecurityService::ClearPreloads()
1667 : {
1668 : // Child processes are not allowed direct access to this.
1669 0 : if (!XRE_IsParentProcess()) {
1670 0 : MOZ_CRASH("Child process: no direct access to nsISiteSecurityService::ClearPreloads");
1671 : }
1672 :
1673 0 : return mPreloadStateStorage->Clear();
1674 : }
1675 :
1676 0 : bool entryStateNotOK(SiteHPKPState& state, mozilla::pkix::Time& aEvalTime) {
1677 0 : return state.mState != SecurityPropertySet || state.IsExpired(aEvalTime) ||
1678 0 : state.mSHA256keys.Length() < 1;
1679 : }
1680 :
1681 : NS_IMETHODIMP
1682 0 : nsSiteSecurityService::GetKeyPinsForHostname(
1683 : const nsACString& aHostname,
1684 : mozilla::pkix::Time& aEvalTime,
1685 : const OriginAttributes& aOriginAttributes,
1686 : /*out*/ nsTArray<nsCString>& pinArray,
1687 : /*out*/ bool* aIncludeSubdomains,
1688 : /*out*/ bool* afound)
1689 : {
1690 : // Child processes are not allowed direct access to this.
1691 0 : if (!XRE_IsParentProcess()) {
1692 0 : MOZ_CRASH("Child process: no direct access to "
1693 : "nsISiteSecurityService::GetKeyPinsForHostname");
1694 : }
1695 :
1696 0 : NS_ENSURE_ARG(afound);
1697 :
1698 0 : const nsCString& flatHostname = PromiseFlatCString(aHostname);
1699 0 : SSSLOG(("Top of GetKeyPinsForHostname for %s", flatHostname.get()));
1700 0 : *afound = false;
1701 0 : *aIncludeSubdomains = false;
1702 0 : pinArray.Clear();
1703 :
1704 : nsAutoCString host(
1705 0 : PublicKeyPinningService::CanonicalizeHostname(flatHostname.get()));
1706 0 : nsAutoCString storageKey;
1707 : SetStorageKey(host, nsISiteSecurityService::HEADER_HPKP, aOriginAttributes,
1708 0 : storageKey);
1709 :
1710 0 : SSSLOG(("storagekey '%s'\n", storageKey.get()));
1711 0 : mozilla::DataStorageType storageType = mozilla::DataStorage_Persistent;
1712 0 : nsCString value = mSiteStateStorage->Get(storageKey, storageType);
1713 :
1714 : // decode now
1715 : RefPtr<SiteHPKPState> foundEntry =
1716 0 : new SiteHPKPState(host, aOriginAttributes, value);
1717 0 : if (entryStateNotOK(*foundEntry, aEvalTime)) {
1718 : // not in permanent storage, try now private
1719 0 : value = mSiteStateStorage->Get(storageKey, mozilla::DataStorage_Private);
1720 : RefPtr<SiteHPKPState> privateEntry =
1721 0 : new SiteHPKPState(host, aOriginAttributes, value);
1722 0 : if (entryStateNotOK(*privateEntry, aEvalTime)) {
1723 : // not in private storage, try dynamic preload
1724 0 : nsAutoCString preloadKey;
1725 : SetStorageKey(host, nsISiteSecurityService::HEADER_HPKP,
1726 0 : OriginAttributes(), preloadKey);
1727 0 : value = mPreloadStateStorage->Get(preloadKey,
1728 0 : mozilla::DataStorage_Persistent);
1729 : RefPtr<SiteHPKPState> preloadEntry =
1730 0 : new SiteHPKPState(host, aOriginAttributes, value);
1731 0 : if (entryStateNotOK(*preloadEntry, aEvalTime)) {
1732 0 : return NS_OK;
1733 : }
1734 0 : foundEntry = preloadEntry;
1735 : } else {
1736 0 : foundEntry = privateEntry;
1737 : }
1738 : }
1739 0 : pinArray = foundEntry->mSHA256keys;
1740 0 : *aIncludeSubdomains = foundEntry->mIncludeSubdomains;
1741 0 : *afound = true;
1742 0 : return NS_OK;
1743 : }
1744 :
1745 : NS_IMETHODIMP
1746 0 : nsSiteSecurityService::SetKeyPins(const nsACString& aHost,
1747 : bool aIncludeSubdomains,
1748 : int64_t aExpires, uint32_t aPinCount,
1749 : const char** aSha256Pins,
1750 : bool aIsPreload,
1751 : JS::HandleValue aOriginAttributes,
1752 : JSContext* aCx,
1753 : uint8_t aArgc,
1754 : /*out*/ bool* aResult)
1755 : {
1756 : // Child processes are not allowed direct access to this.
1757 0 : if (!XRE_IsParentProcess()) {
1758 0 : MOZ_CRASH("Child process: no direct access to "
1759 : "nsISiteSecurityService::SetKeyPins");
1760 : }
1761 :
1762 0 : NS_ENSURE_ARG_POINTER(aResult);
1763 0 : NS_ENSURE_ARG_POINTER(aSha256Pins);
1764 0 : OriginAttributes originAttributes;
1765 0 : if (aArgc > 1) {
1766 : // OriginAttributes were passed in.
1767 0 : if (!aOriginAttributes.isObject() ||
1768 0 : !originAttributes.Init(aCx, aOriginAttributes)) {
1769 0 : return NS_ERROR_INVALID_ARG;
1770 : }
1771 : }
1772 0 : if (aIsPreload && originAttributes != OriginAttributes()) {
1773 0 : return NS_ERROR_INVALID_ARG;
1774 : }
1775 :
1776 0 : SSSLOG(("Top of SetKeyPins"));
1777 :
1778 0 : nsTArray<nsCString> sha256keys;
1779 0 : for (unsigned int i = 0; i < aPinCount; i++) {
1780 0 : nsAutoCString pin(aSha256Pins[i]);
1781 0 : SSSLOG(("SetPins pin=%s\n", pin.get()));
1782 0 : if (!stringIsBase64EncodingOf256bitValue(pin)) {
1783 0 : return NS_ERROR_INVALID_ARG;
1784 : }
1785 0 : sha256keys.AppendElement(pin);
1786 : }
1787 : // we always store data in permanent storage (ie no flags)
1788 0 : const nsCString& flatHost = PromiseFlatCString(aHost);
1789 : nsAutoCString host(
1790 0 : PublicKeyPinningService::CanonicalizeHostname(flatHost.get()));
1791 : RefPtr<SiteHPKPState> dynamicEntry = new SiteHPKPState(host, originAttributes,
1792 0 : aExpires, SecurityPropertySet, aIncludeSubdomains, sha256keys);
1793 0 : return SetHPKPState(host.get(), *dynamicEntry, 0, aIsPreload, originAttributes);
1794 : }
1795 :
1796 : NS_IMETHODIMP
1797 0 : nsSiteSecurityService::SetHSTSPreload(const nsACString& aHost,
1798 : bool aIncludeSubdomains,
1799 : int64_t aExpires,
1800 : /*out*/ bool* aResult)
1801 : {
1802 : // Child processes are not allowed direct access to this.
1803 0 : if (!XRE_IsParentProcess()) {
1804 0 : MOZ_CRASH("Child process: no direct access to "
1805 : "nsISiteSecurityService::SetHSTSPreload");
1806 : }
1807 :
1808 0 : NS_ENSURE_ARG_POINTER(aResult);
1809 :
1810 0 : SSSLOG(("Top of SetHSTSPreload"));
1811 :
1812 0 : const nsCString& flatHost = PromiseFlatCString(aHost);
1813 : nsAutoCString host(
1814 0 : PublicKeyPinningService::CanonicalizeHostname(flatHost.get()));
1815 0 : return SetHSTSState(nsISiteSecurityService::HEADER_HSTS, host.get(), aExpires,
1816 : aIncludeSubdomains, 0, SecurityPropertySet,
1817 0 : SourcePreload, OriginAttributes());
1818 : }
1819 :
1820 : nsresult
1821 0 : nsSiteSecurityService::SetHPKPState(const char* aHost, SiteHPKPState& entry,
1822 : uint32_t aFlags, bool aIsPreload,
1823 : const OriginAttributes& aOriginAttributes)
1824 : {
1825 0 : if (aIsPreload && aOriginAttributes != OriginAttributes()) {
1826 0 : return NS_ERROR_INVALID_ARG;
1827 : }
1828 0 : SSSLOG(("Top of SetPKPState"));
1829 0 : nsAutoCString host(aHost);
1830 0 : nsAutoCString storageKey;
1831 : SetStorageKey(host, nsISiteSecurityService::HEADER_HPKP, aOriginAttributes,
1832 0 : storageKey);
1833 0 : bool isPrivate = aFlags & nsISocketProvider::NO_PERMANENT_STORAGE;
1834 : mozilla::DataStorageType storageType = isPrivate
1835 0 : ? mozilla::DataStorage_Private
1836 0 : : mozilla::DataStorage_Persistent;
1837 0 : nsAutoCString stateString;
1838 0 : entry.ToString(stateString);
1839 :
1840 : nsresult rv;
1841 0 : if (aIsPreload) {
1842 0 : rv = mPreloadStateStorage->Put(storageKey, stateString,
1843 0 : mozilla::DataStorage_Persistent);
1844 : } else {
1845 0 : rv = mSiteStateStorage->Put(storageKey, stateString, storageType);
1846 : }
1847 0 : NS_ENSURE_SUCCESS(rv, rv);
1848 0 : return NS_OK;
1849 : }
1850 :
1851 : NS_IMETHODIMP
1852 0 : nsSiteSecurityService::Enumerate(uint32_t aType,
1853 : nsISimpleEnumerator** aEnumerator)
1854 : {
1855 0 : NS_ENSURE_ARG(aEnumerator);
1856 :
1857 0 : nsAutoCString keySuffix;
1858 0 : switch (aType) {
1859 : case nsISiteSecurityService::HEADER_HSTS:
1860 0 : keySuffix.AssignASCII(kHSTSKeySuffix);
1861 0 : break;
1862 : case nsISiteSecurityService::HEADER_HPKP:
1863 0 : keySuffix.AssignASCII(kHPKPKeySuffix);
1864 0 : break;
1865 : default:
1866 0 : return NS_ERROR_INVALID_ARG;
1867 : }
1868 :
1869 0 : InfallibleTArray<mozilla::dom::DataStorageItem> items;
1870 0 : mSiteStateStorage->GetAll(&items);
1871 :
1872 0 : nsCOMArray<nsISiteSecurityState> states;
1873 0 : for (const mozilla::dom::DataStorageItem& item : items) {
1874 0 : if (!StringEndsWith(item.key(), keySuffix)) {
1875 : // The key does not end with correct suffix, so is not the type we want.
1876 0 : continue;
1877 : }
1878 :
1879 : nsCString origin(
1880 0 : StringHead(item.key(), item.key().Length() - keySuffix.Length()));
1881 0 : nsAutoCString hostname;
1882 0 : OriginAttributes originAttributes;
1883 0 : if (!originAttributes.PopulateFromOrigin(origin, hostname)) {
1884 0 : return NS_ERROR_FAILURE;
1885 : }
1886 :
1887 0 : nsCOMPtr<nsISiteSecurityState> state;
1888 0 : switch(aType) {
1889 : case nsISiteSecurityService::HEADER_HSTS:
1890 0 : state = new SiteHSTSState(hostname, originAttributes, item.value());
1891 0 : break;
1892 : case nsISiteSecurityService::HEADER_HPKP:
1893 0 : state = new SiteHPKPState(hostname, originAttributes, item.value());
1894 0 : break;
1895 : default:
1896 0 : MOZ_ASSERT_UNREACHABLE("SSS:Enumerate got invalid type");
1897 : }
1898 :
1899 0 : states.AppendObject(state);
1900 : }
1901 :
1902 0 : NS_NewArrayEnumerator(aEnumerator, states);
1903 0 : return NS_OK;
1904 : }
1905 :
1906 : //------------------------------------------------------------
1907 : // nsSiteSecurityService::nsIObserver
1908 : //------------------------------------------------------------
1909 :
1910 : NS_IMETHODIMP
1911 0 : nsSiteSecurityService::Observe(nsISupports* /*subject*/, const char* topic,
1912 : const char16_t* /*data*/)
1913 : {
1914 : // Don't access Preferences off the main thread.
1915 0 : if (!NS_IsMainThread()) {
1916 0 : MOZ_ASSERT_UNREACHABLE("Preferences accessed off main thread");
1917 : return NS_ERROR_NOT_SAME_THREAD;
1918 : }
1919 :
1920 0 : if (strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
1921 0 : mUsePreloadList = mozilla::Preferences::GetBool(
1922 : "network.stricttransportsecurity.preloadlist", true);
1923 0 : mPreloadListTimeOffset =
1924 0 : mozilla::Preferences::GetInt("test.currentTimeOffsetSeconds", 0);
1925 0 : mProcessPKPHeadersFromNonBuiltInRoots = mozilla::Preferences::GetBool(
1926 : "security.cert_pinning.process_headers_from_non_builtin_roots", false);
1927 0 : mMaxMaxAge = mozilla::Preferences::GetInt(
1928 : "security.cert_pinning.max_max_age_seconds", kSixtyDaysInSeconds);
1929 : }
1930 :
1931 0 : return NS_OK;
1932 : }
|