summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/nsClientAuthRemember.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'security/manager/ssl/nsClientAuthRemember.cpp')
-rw-r--r--security/manager/ssl/nsClientAuthRemember.cpp363
1 files changed, 363 insertions, 0 deletions
diff --git a/security/manager/ssl/nsClientAuthRemember.cpp b/security/manager/ssl/nsClientAuthRemember.cpp
new file mode 100644
index 0000000000..1c7422fc2f
--- /dev/null
+++ b/security/manager/ssl/nsClientAuthRemember.cpp
@@ -0,0 +1,363 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsClientAuthRemember.h"
+
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/DataStorage.h"
+#include "mozilla/RefPtr.h"
+#include "nsCRT.h"
+#include "nsINSSComponent.h"
+#include "nsPrintfCString.h"
+#include "nsNSSComponent.h"
+#include "nsIObserverService.h"
+#include "nsNetUtil.h"
+#include "nsPromiseFlatString.h"
+#include "nsThreadUtils.h"
+#include "nsStringBuffer.h"
+#include "cert.h"
+#include "nspr.h"
+#include "pk11pub.h"
+#include "certdb.h"
+#include "sechash.h"
+#include "SharedSSLState.h"
+
+#include "nsJSUtils.h"
+
+#ifdef XP_MACOSX
+# include <CoreFoundation/CoreFoundation.h>
+# include <Security/Security.h>
+# include "KeychainSecret.h" // for ScopedCFType
+#endif // XP_MACOSX
+
+using namespace mozilla;
+using namespace mozilla::psm;
+
+NS_IMPL_ISUPPORTS(nsClientAuthRememberService, nsIClientAuthRememberService)
+NS_IMPL_ISUPPORTS(nsClientAuthRemember, nsIClientAuthRememberRecord)
+
+const nsCString nsClientAuthRemember::SentinelValue =
+ "no client certificate"_ns;
+
+NS_IMETHODIMP
+nsClientAuthRemember::GetAsciiHost(/*out*/ nsACString& aAsciiHost) {
+ aAsciiHost = mAsciiHost;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClientAuthRemember::GetDbKey(/*out*/ nsACString& aDBKey) {
+ aDBKey = mDBKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClientAuthRemember::GetEntryKey(/*out*/ nsACString& aEntryKey) {
+ aEntryKey.Assign(mAsciiHost);
+ aEntryKey.Append(',');
+ // This used to include the SHA-256 hash of the server certificate.
+ aEntryKey.Append(',');
+ aEntryKey.Append(mOriginAttributesSuffix);
+ return NS_OK;
+}
+
+nsresult nsClientAuthRememberService::Init() {
+ if (!NS_IsMainThread()) {
+ NS_ERROR("nsClientAuthRememberService::Init called off the main thread");
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ mClientAuthRememberList =
+ mozilla::DataStorage::Get(DataStorageClass::ClientAuthRememberList);
+ nsresult rv = mClientAuthRememberList->Init();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClientAuthRememberService::ForgetRememberedDecision(const nsACString& key) {
+ mClientAuthRememberList->Remove(PromiseFlatCString(key),
+ mozilla::DataStorage_Persistent);
+
+ nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(NS_NSSCOMPONENT_CID));
+ if (!nssComponent) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return nssComponent->ClearSSLExternalAndInternalSessionCache();
+}
+
+NS_IMETHODIMP
+nsClientAuthRememberService::GetDecisions(
+ nsTArray<RefPtr<nsIClientAuthRememberRecord>>& results) {
+ nsTArray<DataStorageItem> decisions;
+ mClientAuthRememberList->GetAll(&decisions);
+
+ for (const DataStorageItem& decision : decisions) {
+ if (decision.type == DataStorageType::DataStorage_Persistent) {
+ RefPtr<nsIClientAuthRememberRecord> tmp =
+ new nsClientAuthRemember(decision.key, decision.value);
+
+ results.AppendElement(tmp);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClientAuthRememberService::ClearRememberedDecisions() {
+ mClientAuthRememberList->Clear();
+ nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(NS_NSSCOMPONENT_CID));
+ if (!nssComponent) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return nssComponent->ClearSSLExternalAndInternalSessionCache();
+}
+
+NS_IMETHODIMP
+nsClientAuthRememberService::DeleteDecisionsByHost(
+ const nsACString& aHostName, JS::Handle<JS::Value> aOriginAttributes,
+ JSContext* aCx) {
+ OriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ DataStorageType storageType = GetDataStorageType(attrs);
+
+ nsTArray<DataStorageItem> decisions;
+ mClientAuthRememberList->GetAll(&decisions);
+
+ for (const DataStorageItem& decision : decisions) {
+ if (decision.type == storageType) {
+ RefPtr<nsIClientAuthRememberRecord> tmp =
+ new nsClientAuthRemember(decision.key, decision.value);
+ nsAutoCString asciiHost;
+ tmp->GetAsciiHost(asciiHost);
+ if (asciiHost.Equals(aHostName)) {
+ mClientAuthRememberList->Remove(decision.key, decision.type);
+ }
+ }
+ }
+ nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(NS_NSSCOMPONENT_CID));
+ if (!nssComponent) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return nssComponent->ClearSSLExternalAndInternalSessionCache();
+}
+
+NS_IMETHODIMP
+nsClientAuthRememberService::RememberDecisionScriptable(
+ const nsACString& aHostName, JS::Handle<JS::Value> aOriginAttributes,
+ nsIX509Cert* aClientCert, JSContext* aCx) {
+ OriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return RememberDecision(aHostName, attrs, aClientCert);
+}
+
+NS_IMETHODIMP
+nsClientAuthRememberService::RememberDecision(
+ const nsACString& aHostName, const OriginAttributes& aOriginAttributes,
+ nsIX509Cert* aClientCert) {
+ if (aHostName.IsEmpty()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // aClientCert == nullptr means: remember that user does not want to use a
+ // cert
+ if (aClientCert) {
+ nsAutoCString dbkey;
+ nsresult rv = aClientCert->GetDbKey(dbkey);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return AddEntryToList(aHostName, aOriginAttributes, dbkey);
+ }
+ return AddEntryToList(aHostName, aOriginAttributes,
+ nsClientAuthRemember::SentinelValue);
+}
+
+#ifdef XP_MACOSX
+// On macOS, users can add "identity preference" items in the keychain. These
+// can be added via the Keychain Access tool. These specify mappings from
+// URLs/wildcards like "*.mozilla.org" to specific client certificates. This
+// function retrieves the preferred client certificate for a hostname by
+// querying a system API that checks for these identity preferences.
+nsresult CheckForPreferredCertificate(const nsACString& aHostName,
+ nsACString& aCertDBKey) {
+ aCertDBKey.Truncate();
+ // SecIdentityCopyPreferred seems to expect a proper URI which it can use
+ // for prefix and wildcard matches.
+ // We don't have the full URL but we can turn the hostname into a URI with
+ // an authority section, so that it matches against macOS identity preferences
+ // like `*.foo.com`. If we know that this connection is always going to be
+ // https, then we should put that in the URI as well, so that it matches
+ // identity preferences like `https://foo.com/` as well. If we can plumb
+ // the path or the full URL into this function we could also match identity
+ // preferences like `https://foo.com/bar/` but for now we cannot.
+ nsPrintfCString fakeUrl("//%s/", PromiseFlatCString(aHostName).get());
+ ScopedCFType<CFStringRef> host(::CFStringCreateWithCString(
+ kCFAllocatorDefault, fakeUrl.get(), kCFStringEncodingUTF8));
+ if (!host) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ ScopedCFType<SecIdentityRef> identity(
+ ::SecIdentityCopyPreferred(host.get(), NULL, NULL));
+ if (!identity) {
+ // No preferred identity for this hostname, leave aCertDBKey empty and
+ // return
+ return NS_OK;
+ }
+ SecCertificateRef certRefRaw = NULL;
+ OSStatus copyResult =
+ ::SecIdentityCopyCertificate(identity.get(), &certRefRaw);
+ ScopedCFType<SecCertificateRef> certRef(certRefRaw);
+ if (copyResult != errSecSuccess || certRef.get() == NULL) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ ScopedCFType<CFDataRef> der(::SecCertificateCopyData(certRef.get()));
+ if (!der) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsTArray<uint8_t> derArray(::CFDataGetBytePtr(der.get()),
+ ::CFDataGetLength(der.get()));
+ nsCOMPtr<nsIX509Cert> cert(new nsNSSCertificate(std::move(derArray)));
+ return cert->GetDbKey(aCertDBKey);
+}
+#endif
+
+void nsClientAuthRememberService::Migrate() {
+ MOZ_ASSERT(NS_IsMainThread());
+ static bool migrated = false;
+ if (migrated) {
+ return;
+ }
+ nsTArray<DataStorageItem> decisions;
+ mClientAuthRememberList->GetAll(&decisions);
+ for (const auto& decision : decisions) {
+ if (decision.type != DataStorage_Persistent) {
+ continue;
+ }
+ RefPtr<nsClientAuthRemember> entry(
+ new nsClientAuthRemember(decision.key, decision.value));
+ nsAutoCString newKey;
+ if (NS_FAILED(entry->GetEntryKey(newKey))) {
+ continue;
+ }
+ if (newKey != decision.key) {
+ mClientAuthRememberList->Remove(decision.key, DataStorage_Persistent);
+ (void)mClientAuthRememberList->Put(newKey, decision.value,
+ DataStorage_Persistent);
+ }
+ }
+ migrated = true;
+}
+
+NS_IMETHODIMP
+nsClientAuthRememberService::HasRememberedDecision(
+ const nsACString& aHostName, const OriginAttributes& aOriginAttributes,
+ nsACString& aCertDBKey, bool* aRetVal) {
+ NS_ENSURE_ARG_POINTER(aRetVal);
+ if (aHostName.IsEmpty()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (!NS_IsMainThread()) {
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ *aRetVal = false;
+ aCertDBKey.Truncate();
+
+ Migrate();
+
+ nsAutoCString entryKey;
+ RefPtr<nsClientAuthRemember> entry(
+ new nsClientAuthRemember(aHostName, aOriginAttributes));
+ nsresult rv = entry->GetEntryKey(entryKey);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ DataStorageType storageType = GetDataStorageType(aOriginAttributes);
+
+ nsCString listEntry = mClientAuthRememberList->Get(entryKey, storageType);
+ if (!listEntry.IsEmpty()) {
+ if (!listEntry.Equals(nsClientAuthRemember::SentinelValue)) {
+ aCertDBKey = listEntry;
+ }
+ *aRetVal = true;
+ return NS_OK;
+ }
+
+#ifdef XP_MACOSX
+ rv = CheckForPreferredCertificate(aHostName, aCertDBKey);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!aCertDBKey.IsEmpty()) {
+ *aRetVal = true;
+ return NS_OK;
+ }
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClientAuthRememberService::HasRememberedDecisionScriptable(
+ const nsACString& aHostName, JS::Handle<JS::Value> aOriginAttributes,
+ nsACString& aCertDBKey, JSContext* aCx, bool* aRetVal) {
+ OriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return HasRememberedDecision(aHostName, attrs, aCertDBKey, aRetVal);
+}
+
+nsresult nsClientAuthRememberService::AddEntryToList(
+ const nsACString& aHostName, const OriginAttributes& aOriginAttributes,
+ const nsACString& aDBKey) {
+ nsAutoCString entryKey;
+ RefPtr<nsClientAuthRemember> entry(
+ new nsClientAuthRemember(aHostName, aOriginAttributes));
+ nsresult rv = entry->GetEntryKey(entryKey);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ DataStorageType storageType = GetDataStorageType(aOriginAttributes);
+
+ nsCString tmpDbKey(aDBKey);
+ rv = mClientAuthRememberList->Put(entryKey, tmpDbKey, storageType);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+bool nsClientAuthRememberService::IsPrivateBrowsingKey(
+ const nsCString& entryKey) {
+ const int32_t separator = entryKey.Find(":");
+ nsCString suffix;
+ if (separator >= 0) {
+ entryKey.Left(suffix, separator);
+ } else {
+ suffix = entryKey;
+ }
+ return OriginAttributes::IsPrivateBrowsing(suffix);
+}
+
+DataStorageType nsClientAuthRememberService::GetDataStorageType(
+ const OriginAttributes& aOriginAttributes) {
+ if (aOriginAttributes.mPrivateBrowsingId > 0) {
+ return DataStorage_Private;
+ }
+ return DataStorage_Persistent;
+}