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