1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "nsCertOverrideService.h"
8
9 #include "NSSCertDBTrustDomain.h"
10 #include "ScopedNSSTypes.h"
11 #include "SharedSSLState.h"
12 #include "mozilla/Assertions.h"
13 #include "mozilla/ScopeExit.h"
14 #include "mozilla/TaskQueue.h"
15 #include "mozilla/Telemetry.h"
16 #include "mozilla/TextUtils.h"
17 #include "mozilla/Tokenizer.h"
18 #include "mozilla/Unused.h"
19 #include "nsAppDirectoryServiceDefs.h"
20 #include "nsCRT.h"
21 #include "nsILineInputStream.h"
22 #ifdef ENABLE_WEBDRIVER
23 # include "nsIMarionette.h"
24 #endif
25 #include "nsIObserver.h"
26 #include "nsIObserverService.h"
27 #include "nsIOutputStream.h"
28 #ifdef ENABLE_WEBDRIVER
29 # include "nsIRemoteAgent.h"
30 #endif
31 #include "nsISafeOutputStream.h"
32 #include "nsIX509Cert.h"
33 #include "nsNSSCertHelper.h"
34 #include "nsNSSCertificate.h"
35 #include "nsNSSComponent.h"
36 #include "nsNetUtil.h"
37 #include "nsStreamUtils.h"
38 #include "nsStringBuffer.h"
39 #include "nsThreadUtils.h"
40
41 using namespace mozilla;
42 using namespace mozilla::psm;
43
44 #define CERT_OVERRIDE_FILE_NAME "cert_override.txt"
45
46 class WriterRunnable : public Runnable {
47 public:
WriterRunnable(nsCertOverrideService * aService,nsCString & aData,nsCOMPtr<nsIFile> aFile)48 WriterRunnable(nsCertOverrideService* aService, nsCString& aData,
49 nsCOMPtr<nsIFile> aFile)
50 : Runnable("nsCertOverrideService::WriterRunnable"),
51 mCertOverrideService(aService),
52 mData(aData),
53 mFile(std::move(aFile)) {}
54
55 NS_IMETHOD
Run()56 Run() override {
57 mCertOverrideService->AssertOnTaskQueue();
58 nsresult rv;
59
60 auto removeShutdownBlockerOnExit =
61 MakeScopeExit([certOverrideService = mCertOverrideService]() {
62 NS_DispatchToMainThread(NS_NewRunnableFunction(
63 "nsCertOverrideService::RemoveShutdownBlocker",
64 [certOverrideService] {
65 certOverrideService->RemoveShutdownBlocker();
66 }));
67 });
68
69 nsCOMPtr<nsIOutputStream> outputStream;
70 rv = NS_NewSafeLocalFileOutputStream(
71 getter_AddRefs(outputStream), mFile,
72 PR_CREATE_FILE | PR_TRUNCATE | PR_WRONLY);
73 NS_ENSURE_SUCCESS(rv, rv);
74
75 const char* ptr = mData.get();
76 uint32_t remaining = mData.Length();
77 uint32_t written = 0;
78 while (remaining > 0) {
79 rv = outputStream->Write(ptr, remaining, &written);
80 NS_ENSURE_SUCCESS(rv, rv);
81 remaining -= written;
82 ptr += written;
83 }
84
85 nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(outputStream);
86 MOZ_ASSERT(safeStream);
87 rv = safeStream->Finish();
88 NS_ENSURE_SUCCESS(rv, rv);
89
90 return NS_OK;
91 }
92
93 private:
94 const RefPtr<nsCertOverrideService> mCertOverrideService;
95 nsCString mData;
96 const nsCOMPtr<nsIFile> mFile;
97 };
98
NS_IMPL_ISUPPORTS(nsCertOverride,nsICertOverride)99 NS_IMPL_ISUPPORTS(nsCertOverride, nsICertOverride)
100
101 NS_IMETHODIMP
102 nsCertOverride::GetAsciiHost(/*out*/ nsACString& aAsciiHost) {
103 aAsciiHost = mAsciiHost;
104 return NS_OK;
105 }
106
107 NS_IMETHODIMP
GetDbKey(nsACString & aDBKey)108 nsCertOverride::GetDbKey(/*out*/ nsACString& aDBKey) {
109 aDBKey = mDBKey;
110 return NS_OK;
111 }
112
113 NS_IMETHODIMP
GetPort(int32_t * aPort)114 nsCertOverride::GetPort(/*out*/ int32_t* aPort) {
115 *aPort = mPort;
116 return NS_OK;
117 }
118
119 NS_IMETHODIMP
GetIsTemporary(bool * aIsTemporary)120 nsCertOverride::GetIsTemporary(/*out*/ bool* aIsTemporary) {
121 *aIsTemporary = mIsTemporary;
122 return NS_OK;
123 }
124
125 NS_IMETHODIMP
GetHostPort(nsACString & aHostPort)126 nsCertOverride::GetHostPort(/*out*/ nsACString& aHostPort) {
127 nsCertOverrideService::GetHostWithPort(mAsciiHost, mPort, aHostPort);
128 return NS_OK;
129 }
130
131 NS_IMETHODIMP
GetOriginAttributes(JSContext * aCtx,JS::MutableHandle<JS::Value> aValue)132 nsCertOverride::GetOriginAttributes(
133 JSContext* aCtx, /*out*/ JS::MutableHandle<JS::Value> aValue) {
134 if (ToJSValue(aCtx, mOriginAttributes, aValue)) {
135 return NS_OK;
136 }
137 return NS_ERROR_FAILURE;
138 }
139
convertBitsToString(OverrideBits ob,nsACString & str)140 void nsCertOverride::convertBitsToString(OverrideBits ob,
141 /*out*/ nsACString& str) {
142 str.Truncate();
143
144 if (ob & OverrideBits::Mismatch) {
145 str.Append('M');
146 }
147
148 if (ob & OverrideBits::Untrusted) {
149 str.Append('U');
150 }
151
152 if (ob & OverrideBits::Time) {
153 str.Append('T');
154 }
155 }
156
convertStringToBits(const nsACString & str,OverrideBits & ob)157 void nsCertOverride::convertStringToBits(const nsACString& str,
158 /*out*/ OverrideBits& ob) {
159 ob = OverrideBits::None;
160
161 for (uint32_t i = 0; i < str.Length(); i++) {
162 switch (str.CharAt(i)) {
163 case 'm':
164 case 'M':
165 ob |= OverrideBits::Mismatch;
166 break;
167
168 case 'u':
169 case 'U':
170 ob |= OverrideBits::Untrusted;
171 break;
172
173 case 't':
174 case 'T':
175 ob |= OverrideBits::Time;
176 break;
177
178 default:
179 break;
180 }
181 }
182 }
183
NS_IMPL_ISUPPORTS(nsCertOverrideService,nsICertOverrideService,nsIObserver,nsISupportsWeakReference,nsIAsyncShutdownBlocker)184 NS_IMPL_ISUPPORTS(nsCertOverrideService, nsICertOverrideService, nsIObserver,
185 nsISupportsWeakReference, nsIAsyncShutdownBlocker)
186
187 nsCertOverrideService::nsCertOverrideService()
188 : mMutex("nsCertOverrideService.mutex"),
189 mDisableAllSecurityCheck(false),
190 mPendingWriteCount(0) {
191 nsCOMPtr<nsIEventTarget> target =
192 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
193 MOZ_ASSERT(target);
194
195 mWriterTaskQueue = new TaskQueue(target.forget(), "CertOverrideService");
196 }
197
198 nsCertOverrideService::~nsCertOverrideService() = default;
199
GetShutdownBarrier()200 static nsCOMPtr<nsIAsyncShutdownClient> GetShutdownBarrier() {
201 MOZ_ASSERT(NS_IsMainThread());
202 nsCOMPtr<nsIAsyncShutdownService> svc =
203 mozilla::services::GetAsyncShutdownService();
204 MOZ_RELEASE_ASSERT(svc);
205
206 nsCOMPtr<nsIAsyncShutdownClient> barrier;
207 nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(barrier));
208
209 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
210 MOZ_RELEASE_ASSERT(barrier);
211 return barrier;
212 }
213
Init()214 nsresult nsCertOverrideService::Init() {
215 if (!NS_IsMainThread()) {
216 MOZ_ASSERT_UNREACHABLE("nsCertOverrideService initialized off main thread");
217 return NS_ERROR_NOT_SAME_THREAD;
218 }
219
220 nsCOMPtr<nsIObserverService> observerService =
221 mozilla::services::GetObserverService();
222
223 // If we cannot add ourselves as a profile change observer, then we will not
224 // attempt to read/write any settings file. Otherwise, we would end up
225 // reading/writing the wrong settings file after a profile change.
226 if (observerService) {
227 observerService->AddObserver(this, "profile-before-change", true);
228 observerService->AddObserver(this, "profile-do-change", true);
229 // simulate a profile change so we read the current profile's settings file
230 Observe(nullptr, "profile-do-change", nullptr);
231 }
232
233 SharedSSLState::NoteCertOverrideServiceInstantiated();
234 return NS_OK;
235 }
236
237 NS_IMETHODIMP
Observe(nsISupports *,const char * aTopic,const char16_t * aData)238 nsCertOverrideService::Observe(nsISupports*, const char* aTopic,
239 const char16_t* aData) {
240 // check the topic
241 if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
242 // The profile is about to change,
243 // or is going away because the application is shutting down.
244
245 RemoveAllFromMemory();
246 } else if (!nsCRT::strcmp(aTopic, "profile-do-change")) {
247 // The profile has already changed.
248 // Now read from the new profile location.
249 // we also need to update the cached file location
250
251 MutexAutoLock lock(mMutex);
252
253 nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
254 getter_AddRefs(mSettingsFile));
255 if (NS_SUCCEEDED(rv)) {
256 mSettingsFile->AppendNative(nsLiteralCString(CERT_OVERRIDE_FILE_NAME));
257 } else {
258 mSettingsFile = nullptr;
259 }
260 Read(lock);
261 CountPermanentOverrideTelemetry(lock);
262 }
263
264 return NS_OK;
265 }
266
RemoveAllFromMemory()267 void nsCertOverrideService::RemoveAllFromMemory() {
268 MutexAutoLock lock(mMutex);
269 mSettingsTable.Clear();
270 }
271
RemoveAllTemporaryOverrides()272 void nsCertOverrideService::RemoveAllTemporaryOverrides() {
273 MutexAutoLock lock(mMutex);
274 for (auto iter = mSettingsTable.Iter(); !iter.Done(); iter.Next()) {
275 nsCertOverrideEntry* entry = iter.Get();
276 if (entry->mSettings->mIsTemporary) {
277 entry->mSettings->mCert = nullptr;
278 iter.Remove();
279 }
280 }
281 // no need to write, as temporaries are never written to disk
282 }
283
284 static const char sSHA256OIDString[] = "OID.2.16.840.1.101.3.4.2.1";
Read(const MutexAutoLock & aProofOfLock)285 nsresult nsCertOverrideService::Read(const MutexAutoLock& aProofOfLock) {
286 // If we don't have a profile, then we won't try to read any settings file.
287 if (!mSettingsFile) return NS_OK;
288
289 nsresult rv;
290 nsCOMPtr<nsIInputStream> fileInputStream;
291 rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream),
292 mSettingsFile);
293 if (NS_FAILED(rv)) {
294 return rv;
295 }
296
297 nsCOMPtr<nsILineInputStream> lineInputStream =
298 do_QueryInterface(fileInputStream, &rv);
299 if (NS_FAILED(rv)) {
300 return rv;
301 }
302
303 nsAutoCString buffer;
304 bool isMore = true;
305
306 /* file format is:
307 *
308 * host:port:originattributes \t fingerprint-algorithm \t fingerprint \t
309 * override-mask \t dbKey
310 *
311 * where override-mask is a sequence of characters,
312 * M meaning hostname-Mismatch-override
313 * U meaning Untrusted-override
314 * T meaning Time-error-override (expired/not yet valid)
315 *
316 * if this format isn't respected we move onto the next line in the file.
317 */
318
319 while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) {
320 if (buffer.IsEmpty() || buffer.First() == '#') {
321 continue;
322 }
323
324 Tokenizer parser(buffer);
325 nsDependentCSubstring host;
326 if (parser.CheckChar('[')) { // this is a IPv6 address
327 if (!parser.ReadUntil(Tokenizer::Token::Char(']'), host) ||
328 host.Length() == 0 || !parser.CheckChar(':')) {
329 continue;
330 }
331 } else if (!parser.ReadUntil(Tokenizer::Token::Char(':'), host) ||
332 host.Length() == 0) {
333 continue;
334 }
335 int32_t port = -1;
336 if (!parser.ReadInteger(&port)) {
337 continue;
338 }
339 OriginAttributes attributes;
340 if (parser.CheckChar(':')) {
341 nsDependentCSubstring attributesString;
342 if (!parser.ReadUntil(Tokenizer::Token::Whitespace(), attributesString) ||
343 !attributes.PopulateFromSuffix(attributesString)) {
344 continue;
345 }
346 } else if (!parser.CheckWhite()) {
347 continue;
348 }
349 nsDependentCSubstring algorithm;
350 if (!parser.ReadUntil(Tokenizer::Token::Whitespace(), algorithm) ||
351 algorithm != sSHA256OIDString) {
352 continue;
353 }
354 nsDependentCSubstring fingerprint;
355 if (!parser.ReadUntil(Tokenizer::Token::Whitespace(), fingerprint) ||
356 fingerprint.Length() == 0) {
357 continue;
358 }
359 nsDependentCSubstring bitsString;
360 if (!parser.ReadUntil(Tokenizer::Token::Whitespace(), bitsString) ||
361 bitsString.Length() == 0) {
362 continue;
363 }
364 nsDependentCSubstring dbKey;
365 if (!parser.ReadUntil(Tokenizer::Token::EndOfFile(), dbKey) ||
366 dbKey.Length() == 0) {
367 continue;
368 }
369 nsCertOverride::OverrideBits bits;
370 nsCertOverride::convertStringToBits(bitsString, bits);
371
372 AddEntryToList(host, port, attributes,
373 nullptr, // don't have the cert
374 false, // not temporary
375 fingerprint, bits, dbKey, aProofOfLock);
376 }
377
378 return NS_OK;
379 }
380
Write(const MutexAutoLock & aProofOfLock)381 nsresult nsCertOverrideService::Write(const MutexAutoLock& aProofOfLock) {
382 MOZ_ASSERT(NS_IsMainThread());
383 if (!NS_IsMainThread()) {
384 return NS_ERROR_NOT_SAME_THREAD;
385 }
386
387 // If we don't have any profile, then we won't try to write any file
388 if (!mSettingsFile) {
389 return NS_OK;
390 }
391
392 nsCString output;
393
394 static const char kHeader[] =
395 "# PSM Certificate Override Settings file" NS_LINEBREAK
396 "# This is a generated file! Do not edit." NS_LINEBREAK;
397
398 /* see ::Read for file format */
399
400 output.Append(kHeader);
401
402 static const char kTab[] = "\t";
403 for (auto iter = mSettingsTable.Iter(); !iter.Done(); iter.Next()) {
404 nsCertOverrideEntry* entry = iter.Get();
405
406 RefPtr<nsCertOverride> settings = entry->mSettings;
407 if (settings->mIsTemporary) {
408 continue;
409 }
410
411 nsAutoCString bitsString;
412 nsCertOverride::convertBitsToString(settings->mOverrideBits, bitsString);
413
414 output.Append(entry->mKeyString);
415 output.Append(kTab);
416 output.Append(sSHA256OIDString);
417 output.Append(kTab);
418 output.Append(settings->mFingerprint);
419 output.Append(kTab);
420 output.Append(bitsString);
421 output.Append(kTab);
422 output.Append(settings->mDBKey);
423 output.Append(NS_LINEBREAK);
424 }
425
426 // Make a clone of the file to pass to the WriterRunnable.
427 nsCOMPtr<nsIFile> file;
428 nsresult rv;
429 rv = mSettingsFile->Clone(getter_AddRefs(file));
430 NS_ENSURE_SUCCESS(rv, rv);
431
432 nsCOMPtr<nsIRunnable> runnable = new WriterRunnable(this, output, file);
433 rv = mWriterTaskQueue->Dispatch(runnable.forget());
434 if (NS_FAILED(rv)) {
435 return rv;
436 }
437 mPendingWriteCount++;
438
439 if (mPendingWriteCount == 1) {
440 rv = GetShutdownBarrier()->AddBlocker(
441 this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__,
442 u"nsCertOverrideService writing data"_ns);
443 NS_ENSURE_SUCCESS(rv, rv);
444 }
445
446 return NS_OK;
447 }
448
449 NS_IMETHODIMP
RememberValidityOverride(const nsACString & aHostName,int32_t aPort,const OriginAttributes & aOriginAttributes,nsIX509Cert * aCert,uint32_t aOverrideBits,bool aTemporary)450 nsCertOverrideService::RememberValidityOverride(
451 const nsACString& aHostName, int32_t aPort,
452 const OriginAttributes& aOriginAttributes, nsIX509Cert* aCert,
453 uint32_t aOverrideBits, bool aTemporary) {
454 NS_ENSURE_ARG_POINTER(aCert);
455 if (aHostName.IsEmpty() || !IsAscii(aHostName)) {
456 return NS_ERROR_INVALID_ARG;
457 }
458 if (aPort < -1) {
459 return NS_ERROR_INVALID_ARG;
460 }
461 if (!NS_IsMainThread()) {
462 return NS_ERROR_NOT_SAME_THREAD;
463 }
464
465 UniqueCERTCertificate nsscert(aCert->GetCert());
466 if (!nsscert) {
467 return NS_ERROR_FAILURE;
468 }
469
470 nsAutoCString nickname;
471 nsresult rv = DefaultServerNicknameForCert(nsscert.get(), nickname);
472 if (!aTemporary && NS_SUCCEEDED(rv)) {
473 UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
474 if (!slot) {
475 return NS_ERROR_FAILURE;
476 }
477
478 // This can fail (for example, if we're in read-only mode). Luckily, we
479 // don't even need it to succeed - we always match on the stored hash of the
480 // certificate rather than the full certificate. It makes the display a bit
481 // less informative (since we won't have a certificate to display), but it's
482 // better than failing the entire operation.
483 Unused << PK11_ImportCert(slot.get(), nsscert.get(), CK_INVALID_HANDLE,
484 nickname.get(), false);
485 }
486
487 nsAutoCString fpStr;
488 rv = GetCertSha256Fingerprint(aCert, fpStr);
489 if (NS_FAILED(rv)) {
490 return rv;
491 }
492
493 nsAutoCString dbkey;
494 rv = aCert->GetDbKey(dbkey);
495 if (NS_FAILED(rv)) {
496 return rv;
497 }
498
499 {
500 MutexAutoLock lock(mMutex);
501 AddEntryToList(aHostName, aPort, aOriginAttributes,
502 aTemporary ? aCert : nullptr,
503 // keep a reference to the cert for temporary overrides
504 aTemporary, fpStr,
505 (nsCertOverride::OverrideBits)aOverrideBits, dbkey, lock);
506 if (!aTemporary) {
507 Write(lock);
508 }
509 }
510
511 return NS_OK;
512 }
513
514 NS_IMETHODIMP
RememberValidityOverrideScriptable(const nsACString & aHostName,int32_t aPort,JS::Handle<JS::Value> aOriginAttributes,nsIX509Cert * aCert,uint32_t aOverrideBits,bool aTemporary,JSContext * aCx)515 nsCertOverrideService::RememberValidityOverrideScriptable(
516 const nsACString& aHostName, int32_t aPort,
517 JS::Handle<JS::Value> aOriginAttributes, nsIX509Cert* aCert,
518 uint32_t aOverrideBits, bool aTemporary, JSContext* aCx) {
519 OriginAttributes attrs;
520 if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
521 return NS_ERROR_INVALID_ARG;
522 }
523
524 return RememberValidityOverride(aHostName, aPort, attrs, aCert, aOverrideBits,
525 aTemporary);
526 }
527
528 NS_IMETHODIMP
RememberTemporaryValidityOverrideUsingFingerprint(const nsACString & aHostName,int32_t aPort,const OriginAttributes & aOriginAttributes,const nsACString & aCertFingerprint,uint32_t aOverrideBits)529 nsCertOverrideService::RememberTemporaryValidityOverrideUsingFingerprint(
530 const nsACString& aHostName, int32_t aPort,
531 const OriginAttributes& aOriginAttributes,
532 const nsACString& aCertFingerprint, uint32_t aOverrideBits) {
533 if (aCertFingerprint.IsEmpty() || aHostName.IsEmpty() ||
534 !IsAscii(aCertFingerprint) || !IsAscii(aHostName) || (aPort < -1)) {
535 return NS_ERROR_INVALID_ARG;
536 }
537
538 MutexAutoLock lock(mMutex);
539 AddEntryToList(aHostName, aPort, aOriginAttributes,
540 nullptr, // No cert to keep alive
541 true, // temporary
542 aCertFingerprint, (nsCertOverride::OverrideBits)aOverrideBits,
543 ""_ns, // dbkey
544 lock);
545
546 return NS_OK;
547 }
548
549 NS_IMETHODIMP
550 nsCertOverrideService::
RememberTemporaryValidityOverrideUsingFingerprintScriptable(const nsACString & aHostName,int32_t aPort,JS::Handle<JS::Value> aOriginAttributes,const nsACString & aCertFingerprint,uint32_t aOverrideBits,JSContext * aCx)551 RememberTemporaryValidityOverrideUsingFingerprintScriptable(
552 const nsACString& aHostName, int32_t aPort,
553 JS::Handle<JS::Value> aOriginAttributes,
554 const nsACString& aCertFingerprint, uint32_t aOverrideBits,
555 JSContext* aCx) {
556 OriginAttributes attrs;
557 if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
558 return NS_ERROR_INVALID_ARG;
559 }
560
561 return RememberTemporaryValidityOverrideUsingFingerprint(
562 aHostName, aPort, attrs, aCertFingerprint, aOverrideBits);
563 }
564
565 NS_IMETHODIMP
HasMatchingOverride(const nsACString & aHostName,int32_t aPort,const OriginAttributes & aOriginAttributes,nsIX509Cert * aCert,uint32_t * aOverrideBits,bool * aIsTemporary,bool * aRetval)566 nsCertOverrideService::HasMatchingOverride(
567 const nsACString& aHostName, int32_t aPort,
568 const OriginAttributes& aOriginAttributes, nsIX509Cert* aCert,
569 uint32_t* aOverrideBits, bool* aIsTemporary, bool* aRetval) {
570 bool disableAllSecurityCheck = false;
571 {
572 MutexAutoLock lock(mMutex);
573 disableAllSecurityCheck = mDisableAllSecurityCheck;
574 }
575 if (disableAllSecurityCheck) {
576 nsCertOverride::OverrideBits all = nsCertOverride::OverrideBits::Untrusted |
577 nsCertOverride::OverrideBits::Mismatch |
578 nsCertOverride::OverrideBits::Time;
579 *aOverrideBits = static_cast<uint32_t>(all);
580 *aIsTemporary = false;
581 *aRetval = true;
582 return NS_OK;
583 }
584
585 if (aHostName.IsEmpty() || !IsAscii(aHostName)) {
586 return NS_ERROR_INVALID_ARG;
587 }
588 if (aPort < -1) return NS_ERROR_INVALID_ARG;
589
590 NS_ENSURE_ARG_POINTER(aCert);
591 NS_ENSURE_ARG_POINTER(aOverrideBits);
592 NS_ENSURE_ARG_POINTER(aIsTemporary);
593 NS_ENSURE_ARG_POINTER(aRetval);
594 *aRetval = false;
595 *aOverrideBits = static_cast<uint32_t>(nsCertOverride::OverrideBits::None);
596
597 RefPtr<nsCertOverride> settings;
598
599 {
600 nsAutoCString keyString;
601 GetKeyString(aHostName, aPort, aOriginAttributes, keyString);
602 MutexAutoLock lock(mMutex);
603 nsCertOverrideEntry* entry = mSettingsTable.GetEntry(keyString.get());
604
605 if (!entry) return NS_OK;
606
607 settings = entry->mSettings;
608 }
609
610 *aOverrideBits = static_cast<uint32_t>(settings->mOverrideBits);
611 *aIsTemporary = settings->mIsTemporary;
612
613 nsAutoCString fpStr;
614 nsresult rv = GetCertSha256Fingerprint(aCert, fpStr);
615 if (NS_FAILED(rv)) {
616 return rv;
617 }
618
619 *aRetval = settings->mFingerprint.Equals(fpStr);
620 return NS_OK;
621 }
622
623 NS_IMETHODIMP
HasMatchingOverrideScriptable(const nsACString & aHostName,int32_t aPort,JS::Handle<JS::Value> aOriginAttributes,nsIX509Cert * aCert,uint32_t * aOverrideBits,bool * aIsTemporary,JSContext * aCx,bool * aRetval)624 nsCertOverrideService::HasMatchingOverrideScriptable(
625 const nsACString& aHostName, int32_t aPort,
626 JS::Handle<JS::Value> aOriginAttributes, nsIX509Cert* aCert,
627 uint32_t* aOverrideBits, bool* aIsTemporary, JSContext* aCx,
628 bool* aRetval) {
629 OriginAttributes attrs;
630 if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
631 return NS_ERROR_INVALID_ARG;
632 }
633
634 return HasMatchingOverride(aHostName, aPort, attrs, aCert, aOverrideBits,
635 aIsTemporary, aRetval);
636 }
637
AddEntryToList(const nsACString & aHostName,int32_t aPort,const OriginAttributes & aOriginAttributes,nsIX509Cert * aCert,const bool aIsTemporary,const nsACString & fingerprint,nsCertOverride::OverrideBits ob,const nsACString & dbKey,const MutexAutoLock & aProofOfLock)638 nsresult nsCertOverrideService::AddEntryToList(
639 const nsACString& aHostName, int32_t aPort,
640 const OriginAttributes& aOriginAttributes, nsIX509Cert* aCert,
641 const bool aIsTemporary, const nsACString& fingerprint,
642 nsCertOverride::OverrideBits ob, const nsACString& dbKey,
643 const MutexAutoLock& aProofOfLock) {
644 nsAutoCString keyString;
645 GetKeyString(aHostName, aPort, aOriginAttributes, keyString);
646
647 nsCertOverrideEntry* entry = mSettingsTable.PutEntry(keyString.get());
648
649 if (!entry) {
650 NS_ERROR("can't insert a null entry!");
651 return NS_ERROR_OUT_OF_MEMORY;
652 }
653
654 entry->mKeyString = keyString;
655
656 RefPtr<nsCertOverride> settings(new nsCertOverride());
657
658 settings->mAsciiHost = aHostName;
659 settings->mPort = aPort;
660 settings->mOriginAttributes = aOriginAttributes;
661 settings->mIsTemporary = aIsTemporary;
662 settings->mFingerprint = fingerprint;
663 settings->mOverrideBits = ob;
664 settings->mDBKey = dbKey;
665 // remove whitespace from stored dbKey for backwards compatibility
666 settings->mDBKey.StripWhitespace();
667 settings->mCert = aCert;
668 entry->mSettings = settings;
669
670 return NS_OK;
671 }
672
673 NS_IMETHODIMP
ClearValidityOverride(const nsACString & aHostName,int32_t aPort,const OriginAttributes & aOriginAttributes)674 nsCertOverrideService::ClearValidityOverride(
675 const nsACString& aHostName, int32_t aPort,
676 const OriginAttributes& aOriginAttributes) {
677 if (aHostName.IsEmpty() || !IsAscii(aHostName)) {
678 return NS_ERROR_INVALID_ARG;
679 }
680 if (!NS_IsMainThread()) {
681 return NS_ERROR_NOT_SAME_THREAD;
682 }
683
684 if (aPort == 0 && aHostName.EqualsLiteral("all:temporary-certificates")) {
685 RemoveAllTemporaryOverrides();
686 return NS_OK;
687 }
688 nsAutoCString keyString;
689 GetKeyString(aHostName, aPort, aOriginAttributes, keyString);
690 {
691 MutexAutoLock lock(mMutex);
692 mSettingsTable.RemoveEntry(keyString.get());
693 Write(lock);
694 }
695
696 nsCOMPtr<nsINSSComponent> nss(do_GetService(PSM_COMPONENT_CONTRACTID));
697 if (nss) {
698 nss->ClearSSLExternalAndInternalSessionCache();
699 } else {
700 return NS_ERROR_NOT_AVAILABLE;
701 }
702
703 return NS_OK;
704 }
705 NS_IMETHODIMP
ClearValidityOverrideScriptable(const nsACString & aHostName,int32_t aPort,JS::Handle<JS::Value> aOriginAttributes,JSContext * aCx)706 nsCertOverrideService::ClearValidityOverrideScriptable(
707 const nsACString& aHostName, int32_t aPort,
708 JS::Handle<JS::Value> aOriginAttributes, JSContext* aCx) {
709 OriginAttributes attrs;
710 if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
711 return NS_ERROR_INVALID_ARG;
712 }
713
714 return ClearValidityOverride(aHostName, aPort, attrs);
715 }
716
717 NS_IMETHODIMP
ClearAllOverrides()718 nsCertOverrideService::ClearAllOverrides() {
719 if (!NS_IsMainThread()) {
720 return NS_ERROR_NOT_SAME_THREAD;
721 }
722
723 {
724 MutexAutoLock lock(mMutex);
725 mSettingsTable.Clear();
726 Write(lock);
727 }
728
729 nsCOMPtr<nsINSSComponent> nss(do_GetService(PSM_COMPONENT_CONTRACTID));
730 if (nss) {
731 nss->ClearSSLExternalAndInternalSessionCache();
732 } else {
733 return NS_ERROR_NOT_AVAILABLE;
734 }
735
736 return NS_OK;
737 }
738
CountPermanentOverrideTelemetry(const MutexAutoLock & aProofOfLock)739 void nsCertOverrideService::CountPermanentOverrideTelemetry(
740 const MutexAutoLock& aProofOfLock) {
741 uint32_t overrideCount = 0;
742 for (auto iter = mSettingsTable.Iter(); !iter.Done(); iter.Next()) {
743 if (!iter.Get()->mSettings->mIsTemporary) {
744 overrideCount++;
745 }
746 }
747 Telemetry::Accumulate(Telemetry::SSL_PERMANENT_CERT_ERROR_OVERRIDES,
748 overrideCount);
749 }
750
IsDebugger()751 static bool IsDebugger() {
752 #ifdef ENABLE_WEBDRIVER
753 nsCOMPtr<nsIMarionette> marionette = do_GetService(NS_MARIONETTE_CONTRACTID);
754 if (marionette) {
755 bool marionetteRunning = false;
756 marionette->GetRunning(&marionetteRunning);
757 if (marionetteRunning) {
758 return true;
759 }
760 }
761
762 nsCOMPtr<nsIRemoteAgent> agent = do_GetService(NS_REMOTEAGENT_CONTRACTID);
763 if (agent) {
764 bool remoteAgentListening = false;
765 agent->GetListening(&remoteAgentListening);
766 if (remoteAgentListening) {
767 return true;
768 }
769 }
770 #endif
771
772 return false;
773 }
774
775 NS_IMETHODIMP
776 nsCertOverrideService::
SetDisableAllSecurityChecksAndLetAttackersInterceptMyData(bool aDisable)777 SetDisableAllSecurityChecksAndLetAttackersInterceptMyData(bool aDisable) {
778 if (!(PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR") || IsDebugger())) {
779 return NS_ERROR_NOT_AVAILABLE;
780 }
781
782 {
783 MutexAutoLock lock(mMutex);
784 mDisableAllSecurityCheck = aDisable;
785 }
786
787 nsCOMPtr<nsINSSComponent> nss(do_GetService(PSM_COMPONENT_CONTRACTID));
788 if (nss) {
789 nss->ClearSSLExternalAndInternalSessionCache();
790 } else {
791 return NS_ERROR_NOT_AVAILABLE;
792 }
793
794 return NS_OK;
795 }
796
797 NS_IMETHODIMP
GetSecurityCheckDisabled(bool * aDisabled)798 nsCertOverrideService::GetSecurityCheckDisabled(bool* aDisabled) {
799 MutexAutoLock lock(mMutex);
800 *aDisabled = mDisableAllSecurityCheck;
801 return NS_OK;
802 }
803
804 NS_IMETHODIMP
GetOverrides(nsTArray<RefPtr<nsICertOverride>> & retval)805 nsCertOverrideService::GetOverrides(
806 /*out*/ nsTArray<RefPtr<nsICertOverride>>& retval) {
807 MutexAutoLock lock(mMutex);
808 for (auto iter = mSettingsTable.Iter(); !iter.Done(); iter.Next()) {
809 const RefPtr<nsICertOverride> settings = iter.Get()->mSettings;
810
811 retval.AppendElement(settings);
812 }
813 return NS_OK;
814 }
815
GetHostWithPort(const nsACString & aHostName,int32_t aPort,nsACString & aRetval)816 void nsCertOverrideService::GetHostWithPort(const nsACString& aHostName,
817 int32_t aPort,
818 nsACString& aRetval) {
819 nsAutoCString hostPort;
820 if (aHostName.Contains(':')) {
821 // if aHostName is an IPv6 address, add brackets to match the internal
822 // representation, which always stores IPv6 addresses with brackets
823 hostPort.Append('[');
824 hostPort.Append(aHostName);
825 hostPort.Append(']');
826 } else {
827 hostPort.Append(aHostName);
828 }
829 if (aPort == -1) {
830 aPort = 443;
831 }
832 if (!hostPort.IsEmpty()) {
833 hostPort.Append(':');
834 hostPort.AppendInt(aPort);
835 }
836 aRetval.Assign(hostPort);
837 }
838
GetKeyString(const nsACString & aHostName,int32_t aPort,const OriginAttributes & aOriginAttributes,nsACString & aRetval)839 void nsCertOverrideService::GetKeyString(
840 const nsACString& aHostName, int32_t aPort,
841 const OriginAttributes& aOriginAttributes, nsACString& aRetval) {
842 nsAutoCString keyString;
843 GetHostWithPort(aHostName, aPort, keyString);
844 keyString.Append(':');
845 OriginAttributes strippedAttributes(aOriginAttributes);
846 strippedAttributes.StripAttributes(
847 ~OriginAttributes::STRIP_PRIVATE_BROWSING_ID);
848 nsAutoCString attributeSuffix;
849 strippedAttributes.CreateSuffix(attributeSuffix);
850 keyString.Append(attributeSuffix);
851 aRetval.Assign(keyString);
852 }
853
854 // nsIAsyncShutdownBlocker implementation
855 NS_IMETHODIMP
GetName(nsAString & aName)856 nsCertOverrideService::GetName(nsAString& aName) {
857 aName = u"nsCertOverrideService: shutdown"_ns;
858 return NS_OK;
859 }
860
861 NS_IMETHODIMP
GetState(nsIPropertyBag ** aState)862 nsCertOverrideService::GetState(nsIPropertyBag** aState) {
863 if (!aState) {
864 return NS_ERROR_INVALID_ARG;
865 }
866 *aState = nullptr;
867 return NS_OK;
868 }
869
870 NS_IMETHODIMP
BlockShutdown(nsIAsyncShutdownClient *)871 nsCertOverrideService::BlockShutdown(nsIAsyncShutdownClient*) { return NS_OK; }
872
RemoveShutdownBlocker()873 void nsCertOverrideService::RemoveShutdownBlocker() {
874 MOZ_ASSERT(NS_IsMainThread());
875 MOZ_ASSERT(mPendingWriteCount > 0);
876 mPendingWriteCount--;
877 if (mPendingWriteCount == 0) {
878 nsresult rv = GetShutdownBarrier()->RemoveBlocker(this);
879 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
880 }
881 }
882