Bug 1620976 - Create UI for nsClientAuthRememberService r=keeler,fluent-reviewers,johannh

Differential Revision: https://phabricator.services.mozilla.com/D54336
This commit is contained in:
Moritz Birghan 2020-05-19 13:13:39 +00:00
parent d7a0ca85b2
commit 9997066263
13 changed files with 412 additions and 45 deletions

View File

@ -362,6 +362,10 @@ button > hbox > label {
min-height: 0;
}
#rememberedList > richlistitem {
min-height: 30px !important;
}
.typeIcon {
margin-inline: 10px 9px !important;
}

View File

@ -8,6 +8,9 @@ certmgr-title =
certmgr-tab-mine =
.label = Your Certificates
certmgr-tab-remembered =
.label = Authentication Decisions
certmgr-tab-people =
.label = People
@ -18,6 +21,7 @@ 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
@ -74,6 +78,9 @@ certmgr-delete-cert =
.title = Delete Certificate
.style = width: 48em; height: 24em;
certmgr-cert-host =
.label = Host
certmgr-cert-name =
.label = Certificate Name

View File

@ -44,6 +44,92 @@ var emailTreeView;
*/
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
@ -74,6 +160,14 @@ function LoadCerts() {
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();
}

View File

@ -12,7 +12,7 @@
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">
@ -28,6 +28,7 @@
<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"/>
@ -86,6 +87,34 @@
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"/>

View File

@ -73,7 +73,7 @@ Classes = [
},
{
'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',

View File

@ -19,7 +19,7 @@ XPIDL_SOURCES += [
'nsICertificateDialogs.idl',
'nsICertOverrideService.idl',
'nsIClientAuthDialogs.idl',
'nsIClientAuthRemember.idl',
'nsIClientAuthRememberService.idl',
'nsIContentSignatureVerifier.idl',
'nsICryptoHash.idl',
'nsICryptoHMAC.idl',

View File

@ -25,8 +25,33 @@
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") {}
@ -51,6 +76,31 @@ nsresult nsClientAuthRememberService::Init() {
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) {
@ -79,16 +129,8 @@ nsClientAuthRememberService::ClearRememberedDecisions() {
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();
}
}
@ -151,17 +193,13 @@ nsClientAuthRememberService::HasRememberedDecision(
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;
}
@ -182,10 +220,8 @@ nsresult nsClientAuthRememberService::AddEntryToList(
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;
@ -203,3 +239,15 @@ void nsClientAuthRememberService::GetEntryKey(
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);
}

View File

@ -12,7 +12,7 @@
#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"
@ -25,11 +25,27 @@ 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
@ -70,16 +86,16 @@ class nsClientAuthRememberEntry final : public PLDHashEntryHdr {
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();
@ -90,6 +106,8 @@ class nsClientAuthRememberService final : public nsIObserver,
const nsACString& aFingerprint,
/*out*/ nsACString& aEntryKey);
static bool IsPrivateBrowsingKey(const nsCString& entryKey);
protected:
~nsClientAuthRememberService();
@ -106,11 +124,4 @@ class nsClientAuthRememberService final : public nsIObserver,
const nsACString& aDBKey);
};
#define NS_CLIENTAUTHREMEMBER_CID \
{ /* 1dbc6eb6-0972-4bdb-9dc4-acd0abf72369 */ \
0x1dbc6eb6, 0x0972, 0x4bdb, { \
0x9d, 0xc4, 0xac, 0xd0, 0xab, 0xf7, 0x23, 0x69 \
} \
}
#endif

View File

@ -8,16 +8,38 @@
%{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(1dbc6eb6-0972-4bdb-9dc4-acd0abf72369)]
interface nsIClientAuthRemember : nsISupports
[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 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,

View File

@ -2024,8 +2024,8 @@ nsresult nsNSSComponent::InitializeNSS() {
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"));
@ -2260,8 +2260,8 @@ nsresult nsNSSComponent::LogoutAuthenticatedPK11() {
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();

View File

@ -2336,10 +2336,10 @@ void ClientAuthDataRunnable::RunOnTargetThread() {
// 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) {

View File

@ -14,6 +14,7 @@ skip-if = verify
# certificates.
skip-if = verify
[browser_clientAuth_ui.js]
[browser_clientAuthRememberService.js]
[browser_deleteCert_ui.js]
[browser_downloadCert_ui.js]
[browser_editCACertTrust.js]

View File

@ -0,0 +1,151 @@
// -*- 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() {
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);
});