1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
7 #include "nsClientAuthRemember.h"
9 #include "mozilla/BasePrincipal.h"
10 #include "mozilla/RefPtr.h"
12 #include "nsINSSComponent.h"
13 #include "nsPrintfCString.h"
14 #include "nsNSSComponent.h"
15 #include "nsIDataStorage.h"
16 #include "nsIObserverService.h"
17 #include "nsNetUtil.h"
18 #include "nsPromiseFlatString.h"
19 #include "nsThreadUtils.h"
26 #include "nsJSUtils.h"
29 # include <CoreFoundation/CoreFoundation.h>
30 # include <Security/Security.h>
31 # include "KeychainSecret.h" // for ScopedCFType
34 using namespace mozilla
;
35 using namespace mozilla::psm
;
37 NS_IMPL_ISUPPORTS(nsClientAuthRememberService
, nsIClientAuthRememberService
)
38 NS_IMPL_ISUPPORTS(nsClientAuthRemember
, nsIClientAuthRememberRecord
)
41 nsClientAuthRemember::GetAsciiHost(/*out*/ nsACString
& aAsciiHost
) {
42 aAsciiHost
= mAsciiHost
;
47 nsClientAuthRemember::GetDbKey(/*out*/ nsACString
& aDBKey
) {
53 nsClientAuthRemember::GetEntryKey(/*out*/ nsACString
& aEntryKey
) {
54 aEntryKey
.Assign(mAsciiHost
);
55 aEntryKey
.Append(',');
56 // This used to include the SHA-256 hash of the server certificate.
57 aEntryKey
.Append(',');
58 aEntryKey
.Append(mOriginAttributesSuffix
);
62 nsresult
nsClientAuthRememberService::Init() {
63 if (!NS_IsMainThread()) {
64 NS_ERROR("nsClientAuthRememberService::Init called off the main thread");
65 return NS_ERROR_NOT_SAME_THREAD
;
68 nsCOMPtr
<nsIDataStorageManager
> dataStorageManager(
69 do_GetService("@mozilla.org/security/datastoragemanager;1"));
70 if (!dataStorageManager
) {
71 return NS_ERROR_FAILURE
;
74 dataStorageManager
->Get(nsIDataStorageManager::ClientAuthRememberList
,
75 getter_AddRefs(mClientAuthRememberList
));
79 if (!mClientAuthRememberList
) {
80 return NS_ERROR_FAILURE
;
87 nsClientAuthRememberService::ForgetRememberedDecision(const nsACString
& key
) {
88 nsresult rv
= mClientAuthRememberList
->Remove(
89 PromiseFlatCString(key
), nsIDataStorage::DataType::Persistent
);
93 nsCOMPtr
<nsINSSComponent
> nssComponent(do_GetService(NS_NSSCOMPONENT_CID
));
95 return NS_ERROR_NOT_AVAILABLE
;
97 return nssComponent
->ClearSSLExternalAndInternalSessionCache();
101 nsClientAuthRememberService::GetDecisions(
102 nsTArray
<RefPtr
<nsIClientAuthRememberRecord
>>& results
) {
103 nsTArray
<RefPtr
<nsIDataStorageItem
>> decisions
;
104 nsresult rv
= mClientAuthRememberList
->GetAll(decisions
);
109 for (const auto& decision
: decisions
) {
110 nsIDataStorage::DataType type
;
111 rv
= decision
->GetType(&type
);
115 if (type
== nsIDataStorage::DataType::Persistent
) {
117 rv
= decision
->GetKey(key
);
122 rv
= decision
->GetValue(value
);
126 RefPtr
<nsIClientAuthRememberRecord
> tmp
=
127 new nsClientAuthRemember(key
, value
);
129 results
.AppendElement(tmp
);
137 nsClientAuthRememberService::ClearRememberedDecisions() {
138 nsresult rv
= mClientAuthRememberList
->Clear();
142 nsCOMPtr
<nsINSSComponent
> nssComponent(do_GetService(NS_NSSCOMPONENT_CID
));
144 return NS_ERROR_NOT_AVAILABLE
;
146 return nssComponent
->ClearSSLExternalAndInternalSessionCache();
150 nsClientAuthRememberService::DeleteDecisionsByHost(
151 const nsACString
& aHostName
, JS::Handle
<JS::Value
> aOriginAttributes
,
153 OriginAttributes attrs
;
154 if (!aOriginAttributes
.isObject() || !attrs
.Init(aCx
, aOriginAttributes
)) {
155 return NS_ERROR_INVALID_ARG
;
157 nsIDataStorage::DataType storageType
= GetDataStorageType(attrs
);
159 nsTArray
<RefPtr
<nsIDataStorageItem
>> decisions
;
160 nsresult rv
= mClientAuthRememberList
->GetAll(decisions
);
165 for (const auto& decision
: decisions
) {
166 nsIDataStorage::DataType type
;
167 nsresult rv
= decision
->GetType(&type
);
171 if (type
== storageType
) {
173 rv
= decision
->GetKey(key
);
178 rv
= decision
->GetValue(value
);
182 RefPtr
<nsIClientAuthRememberRecord
> tmp
=
183 new nsClientAuthRemember(key
, value
);
184 nsAutoCString asciiHost
;
185 tmp
->GetAsciiHost(asciiHost
);
186 if (asciiHost
.Equals(aHostName
)) {
187 rv
= mClientAuthRememberList
->Remove(key
, type
);
194 nsCOMPtr
<nsINSSComponent
> nssComponent(do_GetService(NS_NSSCOMPONENT_CID
));
196 return NS_ERROR_NOT_AVAILABLE
;
198 return nssComponent
->ClearSSLExternalAndInternalSessionCache();
202 nsClientAuthRememberService::RememberDecisionScriptable(
203 const nsACString
& aHostName
, JS::Handle
<JS::Value
> aOriginAttributes
,
204 nsIX509Cert
* aClientCert
, JSContext
* aCx
) {
205 OriginAttributes attrs
;
206 if (!aOriginAttributes
.isObject() || !attrs
.Init(aCx
, aOriginAttributes
)) {
207 return NS_ERROR_INVALID_ARG
;
209 return RememberDecision(aHostName
, attrs
, aClientCert
);
213 nsClientAuthRememberService::RememberDecision(
214 const nsACString
& aHostName
, const OriginAttributes
& aOriginAttributes
,
215 nsIX509Cert
* aClientCert
) {
216 if (aHostName
.IsEmpty()) {
217 return NS_ERROR_INVALID_ARG
;
220 // aClientCert == nullptr means: remember that user does not want to use a
224 nsresult rv
= aClientCert
->GetDbKey(dbkey
);
228 return AddEntryToList(aHostName
, aOriginAttributes
, dbkey
);
230 return AddEntryToList(aHostName
, aOriginAttributes
,
231 nsClientAuthRemember::SentinelValue
);
235 // On macOS, users can add "identity preference" items in the keychain. These
236 // can be added via the Keychain Access tool. These specify mappings from
237 // URLs/wildcards like "*.mozilla.org" to specific client certificates. This
238 // function retrieves the preferred client certificate for a hostname by
239 // querying a system API that checks for these identity preferences.
240 nsresult
CheckForPreferredCertificate(const nsACString
& aHostName
,
241 nsACString
& aCertDBKey
) {
242 aCertDBKey
.Truncate();
243 // SecIdentityCopyPreferred seems to expect a proper URI which it can use
244 // for prefix and wildcard matches.
245 // We don't have the full URL but we can turn the hostname into a URI with
246 // an authority section, so that it matches against macOS identity preferences
247 // like `*.foo.com`. If we know that this connection is always going to be
248 // https, then we should put that in the URI as well, so that it matches
249 // identity preferences like `https://foo.com/` as well. If we can plumb
250 // the path or the full URL into this function we could also match identity
251 // preferences like `https://foo.com/bar/` but for now we cannot.
252 nsPrintfCString
fakeUrl("//%s/", PromiseFlatCString(aHostName
).get());
253 ScopedCFType
<CFStringRef
> host(::CFStringCreateWithCString(
254 kCFAllocatorDefault
, fakeUrl
.get(), kCFStringEncodingUTF8
));
256 return NS_ERROR_UNEXPECTED
;
258 ScopedCFType
<SecIdentityRef
> identity(
259 ::SecIdentityCopyPreferred(host
.get(), NULL
, NULL
));
261 // No preferred identity for this hostname, leave aCertDBKey empty and
265 SecCertificateRef certRefRaw
= NULL
;
266 OSStatus copyResult
=
267 ::SecIdentityCopyCertificate(identity
.get(), &certRefRaw
);
268 ScopedCFType
<SecCertificateRef
> certRef(certRefRaw
);
269 if (copyResult
!= errSecSuccess
|| certRef
.get() == NULL
) {
270 return NS_ERROR_UNEXPECTED
;
272 ScopedCFType
<CFDataRef
> der(::SecCertificateCopyData(certRef
.get()));
274 return NS_ERROR_UNEXPECTED
;
277 nsTArray
<uint8_t> derArray(::CFDataGetBytePtr(der
.get()),
278 ::CFDataGetLength(der
.get()));
279 nsCOMPtr
<nsIX509Cert
> cert(new nsNSSCertificate(std::move(derArray
)));
280 return cert
->GetDbKey(aCertDBKey
);
284 void nsClientAuthRememberService::Migrate() {
285 auto migrated
= mMigrated
.Lock();
291 nsTArray
<RefPtr
<nsIDataStorageItem
>> decisions
;
292 nsresult rv
= mClientAuthRememberList
->GetAll(decisions
);
296 for (const auto& decision
: decisions
) {
297 nsIDataStorage::DataType type
;
298 if (NS_FAILED(decision
->GetType(&type
))) {
301 if (type
!= nsIDataStorage::DataType::Persistent
) {
305 if (NS_FAILED(decision
->GetKey(key
))) {
309 if (NS_FAILED(decision
->GetValue(value
))) {
312 RefPtr
<nsClientAuthRemember
> entry(new nsClientAuthRemember(key
, value
));
313 nsAutoCString newKey
;
314 if (NS_FAILED(entry
->GetEntryKey(newKey
))) {
318 if (NS_FAILED(mClientAuthRememberList
->Remove(
319 key
, nsIDataStorage::DataType::Persistent
))) {
322 if (NS_FAILED(mClientAuthRememberList
->Put(
323 newKey
, value
, nsIDataStorage::DataType::Persistent
))) {
331 nsClientAuthRememberService::HasRememberedDecision(
332 const nsACString
& aHostName
, const OriginAttributes
& aOriginAttributes
,
333 nsACString
& aCertDBKey
, bool* aRetVal
) {
334 NS_ENSURE_ARG_POINTER(aRetVal
);
335 if (aHostName
.IsEmpty()) {
336 return NS_ERROR_INVALID_ARG
;
340 aCertDBKey
.Truncate();
344 nsAutoCString entryKey
;
345 RefPtr
<nsClientAuthRemember
> entry(
346 new nsClientAuthRemember(aHostName
, aOriginAttributes
));
347 nsresult rv
= entry
->GetEntryKey(entryKey
);
351 nsIDataStorage::DataType storageType
= GetDataStorageType(aOriginAttributes
);
353 nsAutoCString listEntry
;
354 rv
= mClientAuthRememberList
->Get(entryKey
, storageType
, listEntry
);
355 if (NS_FAILED(rv
) && rv
!= NS_ERROR_NOT_AVAILABLE
) {
358 if (NS_SUCCEEDED(rv
) && !listEntry
.IsEmpty()) {
359 if (!listEntry
.Equals(nsClientAuthRemember::SentinelValue
)) {
360 aCertDBKey
= listEntry
;
367 rv
= CheckForPreferredCertificate(aHostName
, aCertDBKey
);
371 if (!aCertDBKey
.IsEmpty()) {
381 nsClientAuthRememberService::HasRememberedDecisionScriptable(
382 const nsACString
& aHostName
, JS::Handle
<JS::Value
> aOriginAttributes
,
383 nsACString
& aCertDBKey
, JSContext
* aCx
, bool* aRetVal
) {
384 OriginAttributes attrs
;
385 if (!aOriginAttributes
.isObject() || !attrs
.Init(aCx
, aOriginAttributes
)) {
386 return NS_ERROR_INVALID_ARG
;
388 return HasRememberedDecision(aHostName
, attrs
, aCertDBKey
, aRetVal
);
391 nsresult
nsClientAuthRememberService::AddEntryToList(
392 const nsACString
& aHostName
, const OriginAttributes
& aOriginAttributes
,
393 const nsACString
& aDBKey
) {
394 nsAutoCString entryKey
;
395 RefPtr
<nsClientAuthRemember
> entry(
396 new nsClientAuthRemember(aHostName
, aOriginAttributes
));
397 nsresult rv
= entry
->GetEntryKey(entryKey
);
401 nsIDataStorage::DataType storageType
= GetDataStorageType(aOriginAttributes
);
403 nsCString
tmpDbKey(aDBKey
);
404 rv
= mClientAuthRememberList
->Put(entryKey
, tmpDbKey
, storageType
);
412 bool nsClientAuthRememberService::IsPrivateBrowsingKey(
413 const nsCString
& entryKey
) {
414 const int32_t separator
= entryKey
.Find(":");
416 if (separator
>= 0) {
417 entryKey
.Left(suffix
, separator
);
421 return OriginAttributes::IsPrivateBrowsing(suffix
);
424 nsIDataStorage::DataType
nsClientAuthRememberService::GetDataStorageType(
425 const OriginAttributes
& aOriginAttributes
) {
426 if (aOriginAttributes
.IsPrivateBrowsing()) {
427 return nsIDataStorage::DataType::Private
;
429 return nsIDataStorage::DataType::Persistent
;