Bug 1620976 - Create UI for nsClientAuthRememberService r=keeler,fluent-reviewers,johannh
authorMoritz Birghan <mbirghan@mozilla.com>
Tue, 26 May 2020 08:18:24 +0000
changeset 532129 1d9a9f1809247a0a616a1beff2ed400ae4782623
parent 532128 712fe265d041a4c66798f2f364691349b7752a6c
child 532130 68778a6a157b3c39b9231a82f6eb1cdfff5486c8
push id37451
push usercsabou@mozilla.com
push dateTue, 26 May 2020 21:37:52 +0000
treeherdermozilla-central@e803948bb3cd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler, fluent-reviewers, johannh
bugs1620976
milestone78.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1620976 - Create UI for nsClientAuthRememberService r=keeler,fluent-reviewers,johannh Differential Revision: https://phabricator.services.mozilla.com/D54336
browser/themes/shared/preferences/preferences.inc.css
security/manager/locales/en-US/security/certificates/certManager.ftl
security/manager/pki/resources/content/certManager.js
security/manager/pki/resources/content/certManager.xhtml
security/manager/ssl/components.conf
security/manager/ssl/moz.build
security/manager/ssl/nsClientAuthRemember.cpp
security/manager/ssl/nsClientAuthRemember.h
security/manager/ssl/nsIClientAuthRemember.idl
security/manager/ssl/nsIClientAuthRememberService.idl
security/manager/ssl/nsNSSComponent.cpp
security/manager/ssl/nsNSSIOLayer.cpp
security/manager/ssl/tests/mochitest/browser/browser.ini
security/manager/ssl/tests/mochitest/browser/browser_clientAuthRememberService.js
--- a/browser/themes/shared/preferences/preferences.inc.css
+++ b/browser/themes/shared/preferences/preferences.inc.css
@@ -357,16 +357,20 @@ button > hbox > label {
 }
 
 #handlersView > richlistitem > hbox > hbox > menulist {
   margin-block: 0;
   margin-inline-end: 0;
   min-height: 0;
 }
 
+#rememberedList > richlistitem {
+  min-height: 30px !important;
+}
+
 .typeIcon {
   margin-inline: 10px 9px !important;
 }
 
 .actionIcon {
   margin-inline: 11px 8px !important;
 }
 
--- a/security/manager/locales/en-US/security/certificates/certManager.ftl
+++ b/security/manager/locales/en-US/security/certificates/certManager.ftl
@@ -3,26 +3,30 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 certmgr-title =
     .title = Certificate Manager
 
 certmgr-tab-mine =
     .label = Your Certificates
 
+certmgr-tab-remembered =
+    .label = Authentication Decisions
+
 certmgr-tab-people =
     .label = People
 
 certmgr-tab-servers =
     .label = Servers
 
 certmgr-tab-ca =
     .label = Authorities
 
 certmgr-mine = You have certificates from these organizations that identify you
+certmgr-remembered = These certificates are used to identify you to websites
 certmgr-people = You have certificates on file that identify these people
 certmgr-servers = You have certificates on file that identify these servers
 certmgr-ca = You have certificates on file that identify these certificate authorities
 
 certmgr-detail-general-tab-title =
     .label = General
     .accesskey = G
 
@@ -69,16 +73,19 @@ certmgr-edit-cert-trust-ssl =
 
 certmgr-edit-cert-trust-email =
     .label = This certificate can identify mail users.
 
 certmgr-delete-cert =
     .title = Delete Certificate
     .style = width: 48em; height: 24em;
 
+certmgr-cert-host =
+    .label = Host
+
 certmgr-cert-name =
     .label = Certificate Name
 
 certmgr-cert-server =
     .label = Server
 
 certmgr-override-lifetime =
     .label = Lifetime
--- a/security/manager/pki/resources/content/certManager.js
+++ b/security/manager/pki/resources/content/certManager.js
@@ -39,16 +39,102 @@ var serverTreeView;
  */
 var emailTreeView;
 /**
  * Cert tree for the "Your Certificates" tab.
  * @type nsICertTree
  */
 var userTreeView;
 
+var clientAuthRememberService;
+
+var richlist;
+
+var rememberedDecisionsRichList = {
+  buildRichList() {
+    let rememberedDecisions = clientAuthRememberService.getDecisions();
+
+    let oldItems = richlist.querySelectorAll("richlistitem");
+    for (let item of oldItems) {
+      item.remove();
+    }
+
+    let frag = document.createDocumentFragment();
+    for (let decision of rememberedDecisions) {
+      let richlistitem = this._richBoxAddItem(decision);
+      frag.appendChild(richlistitem);
+    }
+    richlist.appendChild(frag);
+  },
+
+  _createItem(item) {
+    let innerHbox = document.createXULElement("hbox");
+    innerHbox.setAttribute("align", "center");
+    innerHbox.setAttribute("flex", "1");
+
+    let row = document.createXULElement("label");
+    row.setAttribute("flex", "1");
+    row.setAttribute("crop", "right");
+    row.setAttribute("style", "margin-inline-start: 15px;");
+    row.setAttribute("value", item);
+    row.setAttribute("ordinal", "1");
+    innerHbox.appendChild(row);
+
+    return innerHbox;
+  },
+
+  _richBoxAddItem(item) {
+    let richlistitem = document.createXULElement("richlistitem");
+
+    richlistitem.setAttribute("entryKey", item.entryKey);
+    richlistitem.setAttribute("dbKey", item.dbKey);
+
+    let hbox = document.createXULElement("hbox");
+    hbox.setAttribute("flex", "1");
+    hbox.setAttribute("equalsize", "always");
+
+    let tmpCert = certdb.findCertByDBKey(item.dbKey);
+
+    hbox.appendChild(this._createItem(item.asciiHost));
+
+    hbox.appendChild(this._createItem(tmpCert.commonName));
+
+    hbox.appendChild(this._createItem(tmpCert.serialNumber));
+
+    richlistitem.appendChild(hbox);
+
+    return richlistitem;
+  },
+
+  deleteSelectedRichListItem() {
+    let selectedItem = richlist.selectedItem;
+    let index = richlist.selectedIndex;
+    if (index < 0) {
+      return;
+    }
+
+    clientAuthRememberService.forgetRememberedDecision(
+      selectedItem.attributes.entryKey.value
+    );
+
+    this.buildRichList();
+  },
+
+  viewSelectedRichListItem() {
+    let selectedItem = richlist.selectedItem;
+    let index = richlist.selectedIndex;
+    if (index < 0) {
+      return;
+    }
+
+    let cert = certdb.findCertByDBKey(selectedItem.attributes.dbKey.value);
+    viewCertHelper(window, cert);
+  },
+};
+
 function LoadCerts() {
   certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
     Ci.nsIX509CertDB
   );
   var certcache = certdb.getCerts();
 
   caTreeView = Cc["@mozilla.org/security/nsCertTree;1"].createInstance(
     Ci.nsICertTree
@@ -69,16 +155,24 @@ function LoadCerts() {
   document.getElementById("email-tree").view = emailTreeView;
 
   userTreeView = Cc["@mozilla.org/security/nsCertTree;1"].createInstance(
     Ci.nsICertTree
   );
   userTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.USER_CERT);
   document.getElementById("user-tree").view = userTreeView;
 
+  clientAuthRememberService = Cc[
+    "@mozilla.org/security/clientAuthRememberService;1"
+  ].getService(Ci.nsIClientAuthRememberService);
+
+  richlist = document.getElementById("rememberedList");
+
+  rememberedDecisionsRichList.buildRichList();
+
   enableBackupAllButton();
 }
 
 function enableBackupAllButton() {
   let backupAllButton = document.getElementById("mine_backupAllButton");
   backupAllButton.disabled = userTreeView.rowCount < 1;
 }
 
--- a/security/manager/pki/resources/content/certManager.xhtml
+++ b/security/manager/pki/resources/content/certManager.xhtml
@@ -7,32 +7,33 @@
 
 <!DOCTYPE window>
 
 <window windowtype="mozilla:certmanager"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         xmlns:html="http://www.w3.org/1999/xhtml"
         data-l10n-id="certmgr-title"
         onload="LoadCerts();"
-        style="min-height: 32em;"
+        style="min-width: 45em; min-height: 32em;"
         persist="screenX screenY width height">
 <dialog id="certmanager"
         buttons="accept">
 
   <linkset>
     <html:link rel="localization" href="security/certificates/certManager.ftl"/>
   </linkset>
 
   <script src="chrome://pippki/content/pippki.js"/>
   <script src="chrome://pippki/content/certManager.js"/>
 
   <vbox flex="1">
     <tabbox id="certmanagertabs" flex="1" style="margin:5px" persist="selectedIndex">
       <tabs id="certMgrTabbox">
         <tab id="mine_tab" data-l10n-id="certmgr-tab-mine"/>
+        <tab id="remembered_tab" data-l10n-id="certmgr-tab-remembered"/>
         <tab id="others_tab" data-l10n-id="certmgr-tab-people"/>
         <tab id="websites_tab" data-l10n-id="certmgr-tab-servers"/>
         <tab id="ca_tab" data-l10n-id="certmgr-tab-ca" selected="true"/>
       </tabs>
       <tabpanels flex="1">
         <vbox id="myCerts" flex="1">
           <description data-l10n-id="certmgr-mine"></description>
           <separator class="thin"/>
@@ -81,16 +82,44 @@
              <button id="mine_restoreButton" class="normal"
                      data-l10n-id="certmgr-restore"
                      oncommand="restoreCerts();"/>
              <button id="mine_deleteButton" class="normal"
                      data-l10n-id="certmgr-delete"
                      disabled="true" oncommand="deleteCerts();"/>
           </hbox>
         </vbox>
+        <vbox id="rememberedCerts" flex="1">
+          <description data-l10n-id="certmgr-remembered"></description>
+          <separator class="thin"/>
+
+           <listheader equalsize="always">
+             <treecol id="hostcol" data-l10n-id="certmgr-cert-host" primary="true"
+                           persist="hidden width ordinal" flex="1"/>
+             <treecol id="certcol" data-l10n-id="certmgr-cert-name" primary="true"
+                           persist="hidden width ordinal" flex="1"/>
+             <treecol id="serialnumcol" data-l10n-id="certmgr-serial"
+                           persist="hidden width ordinal" flex="1"/>
+           </listheader>
+           <richlistbox id="rememberedList" flex="1" selected="false"/>
+
+          <separator class="thin"/>
+
+          <hbox>
+             <button id="remembered_deleteButton" class="normal"
+                     data-l10n-id="certmgr-delete"
+
+                     oncommand="rememberedDecisionsRichList.deleteSelectedRichListItem()"/>
+
+             <button id="remembered_viewButton" class="normal"
+                     data-l10n-id="certmgr-view"
+
+                     oncommand="rememberedDecisionsRichList.viewSelectedRichListItem()"/>
+          </hbox>
+        </vbox>
         <vbox id="othersCerts" flex="1">
           <description data-l10n-id="certmgr-people"></description>
           <separator class="thin"/>
           <tree id="email-tree" flex="1"
                     onselect="email_enableButtons()">
             <treecols>
               <treecol id="certcol" data-l10n-id="certmgr-cert-name" primary="true"
                            flex="1"/>
--- a/security/manager/ssl/components.conf
+++ b/security/manager/ssl/components.conf
@@ -68,17 +68,17 @@ Classes = [
     {
         'cid': '{fb0bbc5c-452e-4783-b32c-80124693d871}',
         'contract_ids': ['@mozilla.org/security/x509certdb;1'],
         'type': 'nsNSSCertificateDB',
         'legacy_constructor': 'mozilla::psm::NSSConstructor<nsNSSCertificateDB>',
     },
     {
         'cid': '{1dbc6eb6-0972-4bdb-9dc4-acd0abf72369}',
-        'contract_ids': ['@mozilla.org/security/clientAuthRemember;1'],
+        'contract_ids': ['@mozilla.org/security/clientAuthRememberService;1'],
         'type': 'nsClientAuthRememberService',
         'headers': ['nsClientAuthRemember.h'],
         'init_method': 'Init',
     },
     {
         'cid': '{36a1d3b3-d886-4317-96ff-87b0005cfef7}',
         'contract_ids': ['@mozilla.org/security/hash;1'],
         'type': 'nsCryptoHash',
--- a/security/manager/ssl/moz.build
+++ b/security/manager/ssl/moz.build
@@ -14,17 +14,17 @@ TEST_DIRS += [ 'tests' ]
 
 XPIDL_SOURCES += [
     'nsIASN1Object.idl',
     'nsIASN1PrintableItem.idl',
     'nsIASN1Sequence.idl',
     'nsICertificateDialogs.idl',
     'nsICertOverrideService.idl',
     'nsIClientAuthDialogs.idl',
-    'nsIClientAuthRemember.idl',
+    'nsIClientAuthRememberService.idl',
     'nsIContentSignatureVerifier.idl',
     'nsICryptoHash.idl',
     'nsICryptoHMAC.idl',
     'nsIKeyModule.idl',
     'nsILocalCertService.idl',
     'nsINSSComponent.idl',
     'nsINSSErrorsService.idl',
     'nsINSSVersion.idl',
--- a/security/manager/ssl/nsClientAuthRemember.cpp
+++ b/security/manager/ssl/nsClientAuthRemember.cpp
@@ -20,18 +20,43 @@
 #include "pk11pub.h"
 #include "certdb.h"
 #include "sechash.h"
 #include "SharedSSLState.h"
 
 using namespace mozilla;
 using namespace mozilla::psm;
 
-NS_IMPL_ISUPPORTS(nsClientAuthRememberService, nsIClientAuthRemember,
+NS_IMPL_ISUPPORTS(nsClientAuthRememberService, nsIClientAuthRememberService,
                   nsIObserver)
+NS_IMPL_ISUPPORTS(nsClientAuthRemember, nsIClientAuthRememberRecord)
+
+NS_IMETHODIMP
+nsClientAuthRemember::GetAsciiHost(/*out*/ nsACString& aAsciiHost) {
+  aAsciiHost = mAsciiHost;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClientAuthRemember::GetFingerprint(/*out*/ nsACString& aFingerprint) {
+  aFingerprint = mFingerprint;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClientAuthRemember::GetDbKey(/*out*/ nsACString& aDBKey) {
+  aDBKey = mDBKey;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClientAuthRemember::GetEntryKey(/*out*/ nsACString& aEntryKey) {
+  aEntryKey = mEntryKey;
+  return NS_OK;
+}
 
 nsClientAuthRememberService::nsClientAuthRememberService()
     : monitor("nsClientAuthRememberService.monitor") {}
 
 nsClientAuthRememberService::~nsClientAuthRememberService() {
   RemoveAllFromMemory();
 }
 
@@ -47,16 +72,41 @@ nsresult nsClientAuthRememberService::In
     observerService->AddObserver(this, "profile-before-change", false);
     observerService->AddObserver(this, "last-pb-context-exited", false);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsClientAuthRememberService::ForgetRememberedDecision(const nsACString& key) {
+  {
+    ReentrantMonitorAutoEnter lock(monitor);
+    mSettingsTable.RemoveEntry(PromiseFlatCString(key).get());
+  }
+
+  nsNSSComponent::ClearSSLExternalAndInternalSessionCacheNative();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClientAuthRememberService::GetDecisions(
+    nsTArray<RefPtr<nsIClientAuthRememberRecord>>& results) {
+  ReentrantMonitorAutoEnter lock(monitor);
+  for (auto iter = mSettingsTable.Iter(); !iter.Done(); iter.Next()) {
+    if (!nsClientAuthRememberService::IsPrivateBrowsingKey(
+            iter.Get()->mEntryKey)) {
+      results.AppendElement(iter.Get()->mSettings);
+    }
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsClientAuthRememberService::Observe(nsISupports* aSubject, const char* aTopic,
                                      const char16_t* aData) {
   // check the topic
   if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
     // The profile is about to change,
     // or is going away because the application is shutting down.
 
     ReentrantMonitorAutoEnter lock(monitor);
@@ -74,26 +124,18 @@ nsClientAuthRememberService::ClearRememb
   ReentrantMonitorAutoEnter lock(monitor);
   RemoveAllFromMemory();
   return NS_OK;
 }
 
 nsresult nsClientAuthRememberService::ClearPrivateDecisions() {
   ReentrantMonitorAutoEnter lock(monitor);
   for (auto iter = mSettingsTable.Iter(); !iter.Done(); iter.Next()) {
-    nsCString entryKey = iter.Get()->mEntryKey;
-    const int32_t separator = entryKey.Find(":", false, 0, -1);
-    nsCString suffix;
-    if (separator >= 0) {
-      entryKey.Left(suffix, separator);
-    } else {
-      suffix = entryKey;
-    }
-
-    if (OriginAttributes::IsPrivateBrowsing(suffix)) {
+    if (nsClientAuthRememberService::IsPrivateBrowsingKey(
+            iter.Get()->mEntryKey)) {
       iter.Remove();
     }
   }
   return NS_OK;
 }
 
 void nsClientAuthRememberService::RemoveAllFromMemory() {
   mSettingsTable.Clear();
@@ -146,27 +188,23 @@ nsClientAuthRememberService::HasRemember
 
   nsresult rv;
   nsAutoCString fpStr;
   rv = GetCertFingerprintByOidTag(aCert, SEC_OID_SHA256, fpStr);
   if (NS_FAILED(rv)) return rv;
 
   nsAutoCString entryKey;
   GetEntryKey(aHostName, aOriginAttributes, fpStr, entryKey);
-  nsClientAuthRemember settings;
-
   {
     ReentrantMonitorAutoEnter lock(monitor);
     nsClientAuthRememberEntry* entry = mSettingsTable.GetEntry(entryKey.get());
     if (!entry) return NS_OK;
-    settings = entry->mSettings;  // copy
+    entry->mSettings->GetDbKey(aCertDBKey);
+    *aRetVal = true;
   }
-
-  aCertDBKey = settings.mDBKey;
-  *aRetVal = true;
   return NS_OK;
 }
 
 nsresult nsClientAuthRememberService::AddEntryToList(
     const nsACString& aHostName, const OriginAttributes& aOriginAttributes,
     const nsACString& aFingerprint, const nsACString& aDBKey) {
   nsAutoCString entryKey;
   GetEntryKey(aHostName, aOriginAttributes, aFingerprint, entryKey);
@@ -177,20 +215,18 @@ nsresult nsClientAuthRememberService::Ad
 
     if (!entry) {
       NS_ERROR("can't insert a null entry!");
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
     entry->mEntryKey = entryKey;
 
-    nsClientAuthRemember& settings = entry->mSettings;
-    settings.mAsciiHost = aHostName;
-    settings.mFingerprint = aFingerprint;
-    settings.mDBKey = aDBKey;
+    entry->mSettings =
+        new nsClientAuthRemember(aHostName, aFingerprint, aDBKey, entryKey);
   }
 
   return NS_OK;
 }
 
 void nsClientAuthRememberService::GetEntryKey(
     const nsACString& aHostName, const OriginAttributes& aOriginAttributes,
     const nsACString& aFingerprint, nsACString& aEntryKey) {
@@ -198,8 +234,20 @@ void nsClientAuthRememberService::GetEnt
   nsAutoCString suffix;
   aOriginAttributes.CreateSuffix(suffix);
   hostCert.Append(suffix);
   hostCert.Append(':');
   hostCert.Append(aFingerprint);
 
   aEntryKey.Assign(hostCert);
 }
+
+bool nsClientAuthRememberService::IsPrivateBrowsingKey(
+    const nsCString& entryKey) {
+  const int32_t separator = entryKey.Find(":", false, 0, -1);
+  nsCString suffix;
+  if (separator >= 0) {
+    entryKey.Left(suffix, separator);
+  } else {
+    suffix = entryKey;
+  }
+  return OriginAttributes::IsPrivateBrowsing(suffix);
+}
--- a/security/manager/ssl/nsClientAuthRemember.h
+++ b/security/manager/ssl/nsClientAuthRemember.h
@@ -7,34 +7,50 @@
 #ifndef __NSCLIENTAUTHREMEMBER_H__
 #define __NSCLIENTAUTHREMEMBER_H__
 
 #include <utility>
 
 #include "mozilla/Attributes.h"
 #include "mozilla/HashFunctions.h"
 #include "mozilla/ReentrantMonitor.h"
-#include "nsIClientAuthRemember.h"
+#include "nsIClientAuthRememberService.h"
 #include "nsIObserver.h"
 #include "nsNSSCertificate.h"
 #include "nsString.h"
 #include "nsTHashtable.h"
 #include "nsWeakReference.h"
 
 namespace mozilla {
 class OriginAttributes;
 }
 
 using mozilla::OriginAttributes;
 
-class nsClientAuthRemember {
+class nsClientAuthRemember final : public nsIClientAuthRememberRecord {
  public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSICLIENTAUTHREMEMBERRECORD
+
+  nsClientAuthRemember(const nsACString& aAsciiHost,
+                       const nsACString& aFingerprint, const nsACString& aDBKey,
+                       const nsACString& aEntryKey) {
+    mAsciiHost = aAsciiHost;
+    mFingerprint = aFingerprint;
+    mDBKey = aDBKey;
+    mEntryKey = aEntryKey;
+  }
+
   nsCString mAsciiHost;
   nsCString mFingerprint;
   nsCString mDBKey;
+  nsCString mEntryKey;
+
+ protected:
+  ~nsClientAuthRemember() = default;
 };
 
 // hash entry class
 class nsClientAuthRememberEntry final : public PLDHashEntryHdr {
  public:
   // Hash methods
   typedef const char* KeyType;
   typedef const char* KeyTypePointer;
@@ -65,52 +81,47 @@ class nsClientAuthRememberEntry final : 
 
   enum { ALLOW_MEMMOVE = false };
 
   // get methods
   inline const nsCString& GetEntryKey() const { return mEntryKey; }
 
   inline KeyTypePointer EntryKeyPtr() const { return mEntryKey.get(); }
 
-  nsClientAuthRemember mSettings;
+  nsCOMPtr<nsIClientAuthRememberRecord> mSettings;
   nsCString mEntryKey;
 };
 
 class nsClientAuthRememberService final : public nsIObserver,
-                                          public nsIClientAuthRemember {
+                                          public nsIClientAuthRememberService {
  public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIOBSERVER
-  NS_DECL_NSICLIENTAUTHREMEMBER
+  NS_DECL_NSICLIENTAUTHREMEMBERSERVICE
 
   nsClientAuthRememberService();
 
   nsresult Init();
 
   static void GetEntryKey(const nsACString& aHostName,
                           const OriginAttributes& aOriginAttributes,
                           const nsACString& aFingerprint,
                           /*out*/ nsACString& aEntryKey);
 
+  static bool IsPrivateBrowsingKey(const nsCString& entryKey);
+
  protected:
   ~nsClientAuthRememberService();
 
   mozilla::ReentrantMonitor monitor;
   nsTHashtable<nsClientAuthRememberEntry> mSettingsTable;
 
   void RemoveAllFromMemory();
 
   nsresult ClearPrivateDecisions();
 
   nsresult AddEntryToList(const nsACString& aHost,
                           const OriginAttributes& aOriginAttributes,
                           const nsACString& aServerFingerprint,
                           const nsACString& aDBKey);
 };
 
-#define NS_CLIENTAUTHREMEMBER_CID                    \
-  { /* 1dbc6eb6-0972-4bdb-9dc4-acd0abf72369 */       \
-    0x1dbc6eb6, 0x0972, 0x4bdb, {                    \
-      0x9d, 0xc4, 0xac, 0xd0, 0xab, 0xf7, 0x23, 0x69 \
-    }                                                \
-  }
-
 #endif
rename from security/manager/ssl/nsIClientAuthRemember.idl
rename to security/manager/ssl/nsIClientAuthRememberService.idl
--- a/security/manager/ssl/nsIClientAuthRemember.idl
+++ b/security/manager/ssl/nsIClientAuthRememberService.idl
@@ -3,26 +3,48 @@
  * 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 "nsISupports.idl"
 
 %{C++
 #include "cert.h"
-#define NS_CLIENTAUTHREMEMBER_CONTRACTID "@mozilla.org/security/clientAuthRemember;1"
+#define NS_CLIENTAUTHREMEMBERSERVICE_CONTRACTID "@mozilla.org/security/clientAuthRememberService;1"
 %}
 
 [ptr] native CERTCertificatePtr(CERTCertificate);
 [ref] native const_OriginAttributesRef(const mozilla::OriginAttributes);
 
+[scriptable, uuid(e92825af-7e81-4b5c-b412-8e1dd36d14fe)]
+interface nsIClientAuthRememberRecord : nsISupports
+{
+
+  readonly attribute ACString asciiHost;
+
+  readonly attribute ACString fingerprint;
+
+  readonly attribute ACString dbKey;
+
+  readonly attribute ACString entryKey;
+
+};
+
+
 [scriptable, uuid(1dbc6eb6-0972-4bdb-9dc4-acd0abf72369)]
-interface nsIClientAuthRemember : nsISupports
+interface nsIClientAuthRememberService : nsISupports
 {
 
+  [must_use]
+  void forgetRememberedDecision(in ACString key);
+
+
+  [must_use]
+  Array<nsIClientAuthRememberRecord> getDecisions();
+
   [must_use, noscript]
   void rememberDecision(in ACString aHostName,
                         in const_OriginAttributesRef aOriginAttributes,
                         in CERTCertificatePtr aServerCert,
                         in CERTCertificatePtr aClientCert);
 
   [must_use, noscript]
   bool hasRememberedDecision(in ACString aHostName,
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -2019,18 +2019,18 @@ nsresult nsNSSComponent::InitializeNSS()
 
   rv = CommonInit();
 
   MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
   if (NS_FAILED(rv)) {
     return NS_ERROR_UNEXPECTED;
   }
 
-  nsCOMPtr<nsIClientAuthRemember> cars =
-      do_GetService(NS_CLIENTAUTHREMEMBER_CONTRACTID);
+  nsCOMPtr<nsIClientAuthRememberService> cars =
+      do_GetService(NS_CLIENTAUTHREMEMBERSERVICE_CONTRACTID);
 
   MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("NSS Initialization done\n"));
 
   {
     MutexAutoLock lock(mMutex);
 
     // ensure we have initial values for various root hashes
 #ifdef DEBUG
@@ -2255,18 +2255,18 @@ nsresult nsNSSComponent::GetNewPrompter(
 nsresult nsNSSComponent::LogoutAuthenticatedPK11() {
   nsCOMPtr<nsICertOverrideService> icos =
       do_GetService("@mozilla.org/security/certoverride;1");
   if (icos) {
     icos->ClearValidityOverride(
         NS_LITERAL_CSTRING("all:temporary-certificates"), 0);
   }
 
-  nsCOMPtr<nsIClientAuthRemember> svc =
-      do_GetService(NS_CLIENTAUTHREMEMBER_CONTRACTID);
+  nsCOMPtr<nsIClientAuthRememberService> svc =
+      do_GetService(NS_CLIENTAUTHREMEMBERSERVICE_CONTRACTID);
 
   if (svc) {
     nsresult rv = svc->ClearRememberedDecisions();
 
     Unused << NS_WARN_IF(NS_FAILED(rv));
   }
 
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
--- a/security/manager/ssl/nsNSSIOLayer.cpp
+++ b/security/manager/ssl/nsNSSIOLayer.cpp
@@ -2331,20 +2331,20 @@ void ClientAuthDataRunnable::RunOnTarget
           PK11_FindKeyByAnyCert(mSelectedCertificate.get(), nullptr));
     }
     return;
   }
 
   // Not Auto => ask
   // Get the SSL Certificate
   const nsACString& hostname = mInfo.HostName();
-  nsCOMPtr<nsIClientAuthRemember> cars = nullptr;
+  nsCOMPtr<nsIClientAuthRememberService> cars = nullptr;
 
   if (mInfo.ProviderTlsFlags() == 0) {
-    cars = do_GetService(NS_CLIENTAUTHREMEMBER_CONTRACTID);
+    cars = do_GetService(NS_CLIENTAUTHREMEMBERSERVICE_CONTRACTID);
   }
 
   if (cars) {
     nsCString rememberedDBKey;
     bool found;
     nsresult rv =
         cars->HasRememberedDecision(hostname, mInfo.OriginAttributesRef(),
                                     mServerCert, rememberedDBKey, &found);
--- a/security/manager/ssl/tests/mochitest/browser/browser.ini
+++ b/security/manager/ssl/tests/mochitest/browser/browser.ini
@@ -9,16 +9,17 @@ support-files =
 [browser_certViewer.js]
 skip-if = verify
 [browser_clientAuth_connection.js]
 # Any test that has to delete certificates (e.g. as part of cleanup) is
 # fundamentally incompatible with verify due to how NSS handles deleting
 # certificates.
 skip-if = verify
 [browser_clientAuth_ui.js]
+[browser_clientAuthRememberService.js]
 [browser_deleteCert_ui.js]
 [browser_downloadCert_ui.js]
 [browser_editCACertTrust.js]
 # An earlier attempt at landing this test resulted in frequent intermittent
 # failures, almost entirely on Linux. See Bug 1309519.
 skip-if = os == "linux"
 [browser_exportP12_passwordUI.js]
 [browser_loadPKCS11Module_ui.js]
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/mochitest/browser/browser_clientAuthRememberService.js
@@ -0,0 +1,152 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+"use strict";
+
+/**
+ * Test certificate (i.e. build/pgo/certs/mochitest.client).
+ * @type nsIX509Cert
+ */
+var cert;
+var cert2;
+var cert3;
+
+var certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
+  Ci.nsIX509CertDB
+);
+
+var deleted = false;
+
+const { MockRegistrar } = ChromeUtils.import(
+  "resource://testing-common/MockRegistrar.jsm"
+);
+
+async function openCertmanager() {
+  let win = window.openDialog("chrome://pippki/content/certManager.xhtml");
+  return new Promise((resolve, reject) => {
+    win.addEventListener(
+      "load",
+      function() {
+        executeSoon(() => resolve(win));
+      },
+      { once: true }
+    );
+  });
+}
+
+function findCertByCommonName(commonName) {
+  for (let cert of certDB.getCerts()) {
+    if (cert.commonName == commonName) {
+      return cert;
+    }
+  }
+  return null;
+}
+
+// Mock implementation of nsIClientAuthRememberService
+const gClientAuthRememberService = {
+  forgetRememberedDecision(key) {
+    deleted = true;
+    Assert.equal(
+      key,
+      "exampleKey2",
+      "Expected to get the same key that was passed in getDecisions()"
+    );
+  },
+
+  getDecisions() {
+    return [
+      {
+        asciiHost: "example.com",
+        dbKey: cert.dbKey,
+        entryKey: "exampleKey1",
+      },
+      {
+        asciiHost: "example.org",
+        dbKey: cert2.dbKey,
+        entryKey: "exampleKey2",
+      },
+      {
+        asciiHost: "example.test",
+        dbKey: cert3.dbKey,
+        entryKey: "exampleKey3",
+      },
+    ];
+  },
+
+  QueryInterface: ChromeUtils.generateQI([Ci.nsIClientAuthRememberService]),
+};
+
+add_task(async function testRememberedDecisionsUI() {
+  cert = findCertByCommonName("Mochitest client");
+  cert2 = await readCertificate("pgo-ca-all-usages.pem", ",,");
+  cert3 = await readCertificate("client-cert-via-intermediate.pem", ",,");
+  isnot(cert, null, "Should be able to find the test client cert");
+  isnot(cert2, null, "Should be able to find pgo-ca-all-usages.pem");
+  isnot(cert3, null, "Should be able to find client-cert-via-intermediate.pem");
+
+  let clientAuthRememberServiceCID = MockRegistrar.register(
+    "@mozilla.org/security/clientAuthRememberService;1",
+    gClientAuthRememberService
+  );
+
+  registerCleanupFunction(() => {
+    MockRegistrar.unregister(clientAuthRememberServiceCID);
+  });
+
+  let win = await openCertmanager();
+
+  let listItems = win.document
+    .getElementById("rememberedList")
+    .querySelectorAll("richlistitem");
+
+  Assert.equal(
+    listItems.length,
+    3,
+    "Expected rememberedList to only have one item"
+  );
+
+  let labels = win.document
+    .getElementById("rememberedList")
+    .querySelectorAll("label");
+
+  Assert.equal(
+    labels.length,
+    9,
+    "Expected the rememberedList to have three labels"
+  );
+
+  let expectedHosts = ["example.com", "example.org", "example.test"];
+  let hosts = [labels[0].value, labels[3].value, labels[6].value];
+  let expectedNames = [cert.commonName, cert2.commonName, cert3.commonName];
+  let names = [labels[1].value, labels[4].value, labels[7].value];
+  let expectedSerialNumbers = [
+    cert.serialNumber,
+    cert2.serialNumber,
+    cert3.serialNumber,
+  ];
+  let serialNumbers = [labels[2].value, labels[5].value, labels[8].value];
+
+  for (let i = 0; i < 3; i++) {
+    Assert.equal(hosts[i], expectedHosts[i], "Expected host to be asciiHost");
+    Assert.equal(
+      names[i],
+      expectedNames[i],
+      "Expected name to be the commonName of the cert"
+    );
+    Assert.equal(
+      serialNumbers[i],
+      expectedSerialNumbers[i],
+      "Expected serialNumber to be the serialNumber of the cert"
+    );
+  }
+
+  win.document.getElementById("rememberedList").selectedIndex = 1;
+
+  win.document.getElementById("remembered_deleteButton").click();
+
+  Assert.ok(deleted, "Expected forgetRememberedDecision() to get called");
+
+  win.document.getElementById("certmanager").acceptDialog();
+  await BrowserTestUtils.windowClosed(win);
+});