1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  *
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 "nsClientAuthRemember.h"
8 
9 #include "mozilla/BasePrincipal.h"
10 #include "mozilla/RefPtr.h"
11 #include "nsCRT.h"
12 #include "nsNSSCertHelper.h"
13 #include "nsIObserverService.h"
14 #include "nsNetUtil.h"
15 #include "nsPromiseFlatString.h"
16 #include "nsThreadUtils.h"
17 #include "nsStringBuffer.h"
18 #include "cert.h"
19 #include "nspr.h"
20 #include "pk11pub.h"
21 #include "certdb.h"
22 #include "sechash.h"
23 #include "SharedSSLState.h"
24 
25 using namespace mozilla;
26 using namespace mozilla::psm;
27 
NS_IMPL_ISUPPORTS(nsClientAuthRememberService,nsIClientAuthRememberService,nsIObserver)28 NS_IMPL_ISUPPORTS(nsClientAuthRememberService, nsIClientAuthRememberService,
29                   nsIObserver)
30 NS_IMPL_ISUPPORTS(nsClientAuthRemember, nsIClientAuthRememberRecord)
31 
32 NS_IMETHODIMP
33 nsClientAuthRemember::GetAsciiHost(/*out*/ nsACString& aAsciiHost) {
34   aAsciiHost = mAsciiHost;
35   return NS_OK;
36 }
37 
38 NS_IMETHODIMP
GetFingerprint(nsACString & aFingerprint)39 nsClientAuthRemember::GetFingerprint(/*out*/ nsACString& aFingerprint) {
40   aFingerprint = mFingerprint;
41   return NS_OK;
42 }
43 
44 NS_IMETHODIMP
GetDbKey(nsACString & aDBKey)45 nsClientAuthRemember::GetDbKey(/*out*/ nsACString& aDBKey) {
46   aDBKey = mDBKey;
47   return NS_OK;
48 }
49 
50 NS_IMETHODIMP
GetEntryKey(nsACString & aEntryKey)51 nsClientAuthRemember::GetEntryKey(/*out*/ nsACString& aEntryKey) {
52   aEntryKey = mEntryKey;
53   return NS_OK;
54 }
55 
nsClientAuthRememberService()56 nsClientAuthRememberService::nsClientAuthRememberService()
57     : monitor("nsClientAuthRememberService.monitor") {}
58 
~nsClientAuthRememberService()59 nsClientAuthRememberService::~nsClientAuthRememberService() {
60   RemoveAllFromMemory();
61 }
62 
Init()63 nsresult nsClientAuthRememberService::Init() {
64   if (!NS_IsMainThread()) {
65     NS_ERROR("nsClientAuthRememberService::Init called off the main thread");
66     return NS_ERROR_NOT_SAME_THREAD;
67   }
68 
69   nsCOMPtr<nsIObserverService> observerService =
70       mozilla::services::GetObserverService();
71   if (observerService) {
72     observerService->AddObserver(this, "profile-before-change", false);
73     observerService->AddObserver(this, "last-pb-context-exited", false);
74   }
75 
76   return NS_OK;
77 }
78 
79 NS_IMETHODIMP
ForgetRememberedDecision(const nsACString & key)80 nsClientAuthRememberService::ForgetRememberedDecision(const nsACString& key) {
81   {
82     ReentrantMonitorAutoEnter lock(monitor);
83     mSettingsTable.RemoveEntry(PromiseFlatCString(key).get());
84   }
85 
86   nsNSSComponent::ClearSSLExternalAndInternalSessionCacheNative();
87   return NS_OK;
88 }
89 
90 NS_IMETHODIMP
GetDecisions(nsTArray<RefPtr<nsIClientAuthRememberRecord>> & results)91 nsClientAuthRememberService::GetDecisions(
92     nsTArray<RefPtr<nsIClientAuthRememberRecord>>& results) {
93   ReentrantMonitorAutoEnter lock(monitor);
94   for (auto iter = mSettingsTable.Iter(); !iter.Done(); iter.Next()) {
95     if (!nsClientAuthRememberService::IsPrivateBrowsingKey(
96             iter.Get()->mEntryKey)) {
97       results.AppendElement(iter.Get()->mSettings);
98     }
99   }
100 
101   return NS_OK;
102 }
103 
104 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)105 nsClientAuthRememberService::Observe(nsISupports* aSubject, const char* aTopic,
106                                      const char16_t* aData) {
107   // check the topic
108   if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
109     // The profile is about to change,
110     // or is going away because the application is shutting down.
111 
112     ReentrantMonitorAutoEnter lock(monitor);
113     RemoveAllFromMemory();
114   } else if (!nsCRT::strcmp(aTopic, "last-pb-context-exited")) {
115     ReentrantMonitorAutoEnter lock(monitor);
116     ClearPrivateDecisions();
117   }
118 
119   return NS_OK;
120 }
121 
122 NS_IMETHODIMP
ClearRememberedDecisions()123 nsClientAuthRememberService::ClearRememberedDecisions() {
124   ReentrantMonitorAutoEnter lock(monitor);
125   RemoveAllFromMemory();
126   return NS_OK;
127 }
128 
ClearPrivateDecisions()129 nsresult nsClientAuthRememberService::ClearPrivateDecisions() {
130   ReentrantMonitorAutoEnter lock(monitor);
131   for (auto iter = mSettingsTable.Iter(); !iter.Done(); iter.Next()) {
132     if (nsClientAuthRememberService::IsPrivateBrowsingKey(
133             iter.Get()->mEntryKey)) {
134       iter.Remove();
135     }
136   }
137   return NS_OK;
138 }
139 
RemoveAllFromMemory()140 void nsClientAuthRememberService::RemoveAllFromMemory() {
141   mSettingsTable.Clear();
142 }
143 
144 NS_IMETHODIMP
RememberDecision(const nsACString & aHostName,const OriginAttributes & aOriginAttributes,CERTCertificate * aServerCert,CERTCertificate * aClientCert)145 nsClientAuthRememberService::RememberDecision(
146     const nsACString& aHostName, const OriginAttributes& aOriginAttributes,
147     CERTCertificate* aServerCert, CERTCertificate* aClientCert) {
148   // aClientCert == nullptr means: remember that user does not want to use a
149   // cert
150   NS_ENSURE_ARG_POINTER(aServerCert);
151   if (aHostName.IsEmpty()) {
152     return NS_ERROR_INVALID_ARG;
153   }
154 
155   nsAutoCString fpStr;
156   nsresult rv = GetCertFingerprintByOidTag(aServerCert, SEC_OID_SHA256, fpStr);
157   if (NS_FAILED(rv)) {
158     return rv;
159   }
160 
161   {
162     ReentrantMonitorAutoEnter lock(monitor);
163     if (aClientCert) {
164       RefPtr<nsNSSCertificate> pipCert(new nsNSSCertificate(aClientCert));
165       nsAutoCString dbkey;
166       rv = pipCert->GetDbKey(dbkey);
167       if (NS_SUCCEEDED(rv)) {
168         AddEntryToList(aHostName, aOriginAttributes, fpStr, dbkey);
169       }
170     } else {
171       nsCString empty;
172       AddEntryToList(aHostName, aOriginAttributes, fpStr, empty);
173     }
174   }
175 
176   return NS_OK;
177 }
178 
179 NS_IMETHODIMP
HasRememberedDecision(const nsACString & aHostName,const OriginAttributes & aOriginAttributes,CERTCertificate * aCert,nsACString & aCertDBKey,bool * aRetVal)180 nsClientAuthRememberService::HasRememberedDecision(
181     const nsACString& aHostName, const OriginAttributes& aOriginAttributes,
182     CERTCertificate* aCert, nsACString& aCertDBKey, bool* aRetVal) {
183   if (aHostName.IsEmpty()) return NS_ERROR_INVALID_ARG;
184 
185   NS_ENSURE_ARG_POINTER(aCert);
186   NS_ENSURE_ARG_POINTER(aRetVal);
187   *aRetVal = false;
188 
189   nsresult rv;
190   nsAutoCString fpStr;
191   rv = GetCertFingerprintByOidTag(aCert, SEC_OID_SHA256, fpStr);
192   if (NS_FAILED(rv)) return rv;
193 
194   nsAutoCString entryKey;
195   GetEntryKey(aHostName, aOriginAttributes, fpStr, entryKey);
196   {
197     ReentrantMonitorAutoEnter lock(monitor);
198     nsClientAuthRememberEntry* entry = mSettingsTable.GetEntry(entryKey.get());
199     if (!entry) return NS_OK;
200     entry->mSettings->GetDbKey(aCertDBKey);
201     *aRetVal = true;
202   }
203   return NS_OK;
204 }
205 
AddEntryToList(const nsACString & aHostName,const OriginAttributes & aOriginAttributes,const nsACString & aFingerprint,const nsACString & aDBKey)206 nsresult nsClientAuthRememberService::AddEntryToList(
207     const nsACString& aHostName, const OriginAttributes& aOriginAttributes,
208     const nsACString& aFingerprint, const nsACString& aDBKey) {
209   nsAutoCString entryKey;
210   GetEntryKey(aHostName, aOriginAttributes, aFingerprint, entryKey);
211 
212   {
213     ReentrantMonitorAutoEnter lock(monitor);
214     nsClientAuthRememberEntry* entry = mSettingsTable.PutEntry(entryKey.get());
215 
216     if (!entry) {
217       NS_ERROR("can't insert a null entry!");
218       return NS_ERROR_OUT_OF_MEMORY;
219     }
220 
221     entry->mEntryKey = entryKey;
222 
223     entry->mSettings =
224         new nsClientAuthRemember(aHostName, aFingerprint, aDBKey, entryKey);
225   }
226 
227   return NS_OK;
228 }
229 
GetEntryKey(const nsACString & aHostName,const OriginAttributes & aOriginAttributes,const nsACString & aFingerprint,nsACString & aEntryKey)230 void nsClientAuthRememberService::GetEntryKey(
231     const nsACString& aHostName, const OriginAttributes& aOriginAttributes,
232     const nsACString& aFingerprint, nsACString& aEntryKey) {
233   nsAutoCString hostCert(aHostName);
234   nsAutoCString suffix;
235   aOriginAttributes.CreateSuffix(suffix);
236   hostCert.Append(suffix);
237   hostCert.Append(':');
238   hostCert.Append(aFingerprint);
239 
240   aEntryKey.Assign(hostCert);
241 }
242 
IsPrivateBrowsingKey(const nsCString & entryKey)243 bool nsClientAuthRememberService::IsPrivateBrowsingKey(
244     const nsCString& entryKey) {
245   const int32_t separator = entryKey.Find(":", false, 0, -1);
246   nsCString suffix;
247   if (separator >= 0) {
248     entryKey.Left(suffix, separator);
249   } else {
250     suffix = entryKey;
251   }
252   return OriginAttributes::IsPrivateBrowsing(suffix);
253 }
254