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