summaryrefslogtreecommitdiffstats
path: root/security/manager/pki/resources/content
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /security/manager/pki/resources/content
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/manager/pki/resources/content')
-rw-r--r--security/manager/pki/resources/content/certManager.css31
-rw-r--r--security/manager/pki/resources/content/certManager.js824
-rw-r--r--security/manager/pki/resources/content/certManager.xhtml225
-rw-r--r--security/manager/pki/resources/content/changepassword.js212
-rw-r--r--security/manager/pki/resources/content/changepassword.xhtml65
-rw-r--r--security/manager/pki/resources/content/clientauthask.js200
-rw-r--r--security/manager/pki/resources/content/clientauthask.xhtml47
-rw-r--r--security/manager/pki/resources/content/deletecert.js121
-rw-r--r--security/manager/pki/resources/content/deletecert.xhtml31
-rw-r--r--security/manager/pki/resources/content/device_manager.js433
-rw-r--r--security/manager/pki/resources/content/device_manager.xhtml70
-rw-r--r--security/manager/pki/resources/content/downloadcert.js83
-rw-r--r--security/manager/pki/resources/content/downloadcert.xhtml61
-rw-r--r--security/manager/pki/resources/content/editcacert.js55
-rw-r--r--security/manager/pki/resources/content/editcacert.xhtml36
-rw-r--r--security/manager/pki/resources/content/exceptionDialog.css38
-rw-r--r--security/manager/pki/resources/content/exceptionDialog.js334
-rw-r--r--security/manager/pki/resources/content/exceptionDialog.xhtml88
-rw-r--r--security/manager/pki/resources/content/load_device.js75
-rw-r--r--security/manager/pki/resources/content/load_device.xhtml53
-rw-r--r--security/manager/pki/resources/content/pippki.js301
-rw-r--r--security/manager/pki/resources/content/resetpassword.js28
-rw-r--r--security/manager/pki/resources/content/resetpassword.xhtml41
-rw-r--r--security/manager/pki/resources/content/setp12password.js127
-rw-r--r--security/manager/pki/resources/content/setp12password.xhtml48
25 files changed, 3627 insertions, 0 deletions
diff --git a/security/manager/pki/resources/content/certManager.css b/security/manager/pki/resources/content/certManager.css
new file mode 100644
index 0000000000..e943c721da
--- /dev/null
+++ b/security/manager/pki/resources/content/certManager.css
@@ -0,0 +1,31 @@
+/* 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/. */
+
+/* Good enough support for equalsize=always for the cert manager use cases.
+ * You probably shouldn't use this as-is elsewhere, this selector is somewhat
+ * slow, it relies on stuff having display: -moz-box, and you probably can use
+ * something simpler if you need this */
+[equalsize="always"] > * {
+ -moz-box-flex: 1;
+ contain: inline-size;
+ /* contain: inline-size should be enough, unless you have a legacy XUL layout
+ * box, which don't support it. */
+ width: 0;
+}
+
+#certmanager {
+ /* This prevents horizontal scrollbars due to <tree> and non-XUL layout
+ * interactions */
+ padding: 0;
+}
+
+/* This matches the <tree> height from dialog.css */
+richlistbox {
+ height: 15em;
+}
+
+richlistbox,
+richlistitem {
+ min-height: 30px;
+}
diff --git a/security/manager/pki/resources/content/certManager.js b/security/manager/pki/resources/content/certManager.js
new file mode 100644
index 0000000000..57d0075add
--- /dev/null
+++ b/security/manager/pki/resources/content/certManager.js
@@ -0,0 +1,824 @@
+/* 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/. */
+/* import-globals-from pippki.js */
+"use strict";
+
+const gCertFileTypes = "*.p7b; *.crt; *.cert; *.cer; *.pem; *.der";
+
+var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+var key;
+
+var certdialogs = Cc["@mozilla.org/nsCertificateDialogs;1"].getService(
+ Ci.nsICertificateDialogs
+);
+
+/**
+ * List of certs currently selected in the active tab.
+ *
+ * @type {nsIX509Cert[]}
+ */
+var selected_certs = [];
+var selected_tree_items = [];
+var selected_index = [];
+var certdb;
+
+/**
+ * Cert tree for the "Authorities" tab.
+ *
+ * @type {nsICertTree}
+ */
+var caTreeView;
+/**
+ * Cert tree for the "Servers" tab.
+ *
+ * @type {nsICertTree}
+ */
+var serverTreeView;
+
+var overrideService;
+
+function createRichlistItem(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;");
+ if ("raw" in item) {
+ row.setAttribute("value", item.raw);
+ } else {
+ document.l10n.setAttributes(row, item.l10nid);
+ }
+ row.setAttribute("ordinal", "1");
+ innerHbox.appendChild(row);
+
+ return innerHbox;
+}
+
+var serverRichList = {
+ richlist: undefined,
+
+ buildRichList() {
+ let overrides = overrideService.getOverrides().map(item => {
+ let cert = null;
+ if (item.dbKey !== "") {
+ cert = certdb.findCertByDBKey(item.dbKey);
+ }
+ return {
+ hostPort: item.hostPort,
+ dbKey: item.dbKey,
+ asciiHost: item.asciiHost,
+ port: item.port,
+ originAttributes: item.originAttributes,
+ isTemporary: item.isTemporary,
+ displayName: cert !== null ? cert.displayName : "",
+ };
+ });
+ overrides.sort((a, b) => {
+ let criteria = ["hostPort", "displayName"];
+ for (let c of criteria) {
+ let res = a[c].localeCompare(b[c]);
+ if (res !== 0) {
+ return res;
+ }
+ }
+ return 0;
+ });
+
+ this.richlist.textContent = "";
+ this.richlist.clearSelection();
+
+ let frag = document.createDocumentFragment();
+ for (let override of overrides) {
+ let richlistitem = this._richBoxAddItem(override);
+ frag.appendChild(richlistitem);
+ }
+ this.richlist.appendChild(frag);
+
+ this._setButtonState();
+ this.richlist.addEventListener("select", () => this._setButtonState());
+ },
+
+ _richBoxAddItem(item) {
+ let richlistitem = document.createXULElement("richlistitem");
+
+ richlistitem.setAttribute("dbKey", item.dbKey);
+ richlistitem.setAttribute("host", item.asciiHost);
+ richlistitem.setAttribute("port", item.port);
+ richlistitem.setAttribute("hostPort", item.hostPort);
+ richlistitem.setAttribute(
+ "originAttributes",
+ JSON.stringify(item.originAttributes)
+ );
+
+ let hbox = document.createXULElement("hbox");
+ hbox.setAttribute("flex", "1");
+ hbox.setAttribute("equalsize", "always");
+
+ hbox.appendChild(createRichlistItem({ raw: item.hostPort }));
+ hbox.appendChild(
+ createRichlistItem(
+ item.displayName !== ""
+ ? { raw: item.displayName }
+ : { l10nid: "no-cert-stored-for-override" }
+ )
+ );
+ hbox.appendChild(
+ createRichlistItem({
+ l10nid: item.isTemporary ? "temporary-override" : "permanent-override",
+ })
+ );
+
+ richlistitem.appendChild(hbox);
+
+ return richlistitem;
+ },
+
+ deleteSelectedRichListItem() {
+ let selectedItem = this.richlist.selectedItem;
+ if (!selectedItem) {
+ return;
+ }
+
+ let retVals = {
+ deleteConfirmed: false,
+ };
+ window.browsingContext.topChromeWindow.openDialog(
+ "chrome://pippki/content/deletecert.xhtml",
+ "",
+ "chrome,centerscreen,modal",
+ "websites_tab",
+ [
+ {
+ hostPort: selectedItem.attributes.hostPort.value,
+ },
+ ],
+ retVals
+ );
+
+ if (retVals.deleteConfirmed) {
+ overrideService.clearValidityOverride(
+ selectedItem.attributes.host.value,
+ selectedItem.attributes.port.value,
+ JSON.parse(selectedItem.attributes.originAttributes.value)
+ );
+ this.buildRichList();
+ }
+ },
+
+ viewSelectedRichListItem() {
+ let selectedItem = this.richlist.selectedItem;
+ if (!selectedItem) {
+ return;
+ }
+
+ let dbKey = selectedItem.getAttribute("dbKey");
+ if (dbKey) {
+ let cert = certdb.findCertByDBKey(dbKey);
+ viewCertHelper(window, cert);
+ }
+ },
+
+ exportSelectedRichListItem() {
+ let selectedItem = this.richlist.selectedItem;
+ if (!selectedItem) {
+ return;
+ }
+
+ let dbKey = selectedItem.getAttribute("dbKey");
+ if (dbKey) {
+ let cert = certdb.findCertByDBKey(dbKey);
+ exportToFile(window, cert);
+ }
+ },
+
+ addException() {
+ let retval = {
+ exceptionAdded: false,
+ };
+ window.browsingContext.topChromeWindow.openDialog(
+ "chrome://pippki/content/exceptionDialog.xhtml",
+ "",
+ "chrome,centerscreen,modal",
+ retval
+ );
+ if (retval.exceptionAdded) {
+ this.buildRichList();
+ }
+ },
+
+ _setButtonState() {
+ let websiteViewButton = document.getElementById("websites_viewButton");
+ let websiteExportButton = document.getElementById("websites_exportButton");
+ let websiteDeleteButton = document.getElementById("websites_deleteButton");
+
+ let certKey = this.richlist.selectedItem?.getAttribute("dbKey");
+ let cert = certKey && certdb.findCertByDBKey(certKey);
+
+ websiteDeleteButton.disabled = this.richlist.selectedIndex < 0;
+ websiteExportButton.disabled = !cert;
+ websiteViewButton.disabled = websiteExportButton.disabled;
+ },
+};
+/**
+ * Cert tree for the "People" tab.
+ *
+ * @type {nsICertTree}
+ */
+var emailTreeView;
+/**
+ * Cert tree for the "Your Certificates" tab.
+ *
+ * @type {nsICertTree}
+ */
+var userTreeView;
+
+var clientAuthRememberService;
+
+var rememberedDecisionsRichList = {
+ richlist: undefined,
+
+ buildRichList() {
+ let rememberedDecisions = clientAuthRememberService.getDecisions();
+
+ let oldItems = this.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);
+ }
+ this.richlist.appendChild(frag);
+
+ this.richlist.addEventListener("select", () => this.setButtonState());
+ },
+
+ _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");
+
+ hbox.appendChild(createRichlistItem({ raw: item.asciiHost }));
+ if (item.dbKey == "") {
+ hbox.appendChild(
+ createRichlistItem({ l10nid: "send-no-client-certificate" })
+ );
+
+ hbox.appendChild(createRichlistItem({ raw: "" }));
+ } else {
+ let tmpCert = certdb.findCertByDBKey(item.dbKey);
+ // The certificate corresponding to this item's dbKey may not be
+ // available (for example, if it was stored on a token that's been
+ // removed, or if it was deleted).
+ if (tmpCert) {
+ hbox.appendChild(createRichlistItem({ raw: tmpCert.commonName }));
+ hbox.appendChild(createRichlistItem({ raw: tmpCert.serialNumber }));
+ } else {
+ hbox.appendChild(
+ createRichlistItem({ l10nid: "certificate-not-available" })
+ );
+ hbox.appendChild(
+ createRichlistItem({ l10nid: "certificate-not-available" })
+ );
+ }
+ }
+
+ richlistitem.appendChild(hbox);
+
+ return richlistitem;
+ },
+
+ deleteSelectedRichListItem() {
+ let selectedItem = this.richlist.selectedItem;
+ let index = this.richlist.selectedIndex;
+ if (index < 0) {
+ return;
+ }
+
+ clientAuthRememberService.forgetRememberedDecision(
+ selectedItem.attributes.entryKey.value
+ );
+
+ this.buildRichList();
+ this.setButtonState();
+ },
+
+ viewSelectedRichListItem() {
+ let selectedItem = this.richlist.selectedItem;
+ let index = this.richlist.selectedIndex;
+ if (index < 0) {
+ return;
+ }
+
+ if (selectedItem.attributes.dbKey.value != "") {
+ let cert = certdb.findCertByDBKey(selectedItem.attributes.dbKey.value);
+ viewCertHelper(window, cert);
+ }
+ },
+
+ setButtonState() {
+ let rememberedDeleteButton = document.getElementById(
+ "remembered_deleteButton"
+ );
+ let rememberedViewButton = document.getElementById("remembered_viewButton");
+
+ rememberedDeleteButton.disabled = this.richlist.selectedIndex < 0;
+ rememberedViewButton.disabled =
+ this.richlist.selectedItem == null
+ ? true
+ : this.richlist.selectedItem.attributes.dbKey.value == "";
+ },
+};
+
+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
+ );
+ caTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.CA_CERT);
+ document.getElementById("ca-tree").view = caTreeView;
+
+ emailTreeView = Cc["@mozilla.org/security/nsCertTree;1"].createInstance(
+ Ci.nsICertTree
+ );
+ emailTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.EMAIL_CERT);
+ 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);
+
+ overrideService = Cc["@mozilla.org/security/certoverride;1"].getService(
+ Ci.nsICertOverrideService
+ );
+
+ rememberedDecisionsRichList.richlist = document.getElementById(
+ "rememberedList"
+ );
+ serverRichList.richlist = document.getElementById("serverList");
+
+ rememberedDecisionsRichList.buildRichList();
+ serverRichList.buildRichList();
+
+ rememberedDecisionsRichList.setButtonState();
+
+ enableBackupAllButton();
+}
+
+function enableBackupAllButton() {
+ let backupAllButton = document.getElementById("mine_backupAllButton");
+ backupAllButton.disabled = userTreeView.rowCount < 1;
+}
+
+function getSelectedCerts() {
+ var ca_tab = document.getElementById("ca_tab");
+ var mine_tab = document.getElementById("mine_tab");
+ var others_tab = document.getElementById("others_tab");
+ var items = null;
+ if (ca_tab.selected) {
+ items = caTreeView.selection;
+ } else if (mine_tab.selected) {
+ items = userTreeView.selection;
+ } else if (others_tab.selected) {
+ items = emailTreeView.selection;
+ }
+ selected_certs = [];
+ var cert = null;
+ var nr = 0;
+ if (items != null) {
+ nr = items.getRangeCount();
+ }
+ if (nr > 0) {
+ for (let i = 0; i < nr; i++) {
+ var o1 = {};
+ var o2 = {};
+ items.getRangeAt(i, o1, o2);
+ var min = o1.value;
+ var max = o2.value;
+ for (let j = min; j <= max; j++) {
+ if (ca_tab.selected) {
+ cert = caTreeView.getCert(j);
+ } else if (mine_tab.selected) {
+ cert = userTreeView.getCert(j);
+ } else if (others_tab.selected) {
+ cert = emailTreeView.getCert(j);
+ }
+ if (cert) {
+ var sc = selected_certs.length;
+ selected_certs[sc] = cert;
+ selected_index[sc] = j;
+ }
+ }
+ }
+ }
+}
+
+function getSelectedTreeItems() {
+ var ca_tab = document.getElementById("ca_tab");
+ var mine_tab = document.getElementById("mine_tab");
+ var others_tab = document.getElementById("others_tab");
+ var items = null;
+ if (ca_tab.selected) {
+ items = caTreeView.selection;
+ } else if (mine_tab.selected) {
+ items = userTreeView.selection;
+ } else if (others_tab.selected) {
+ items = emailTreeView.selection;
+ }
+ selected_certs = [];
+ selected_tree_items = [];
+ selected_index = [];
+ var tree_item = null;
+ var nr = 0;
+ if (items != null) {
+ nr = items.getRangeCount();
+ }
+ if (nr > 0) {
+ for (let i = 0; i < nr; i++) {
+ var o1 = {};
+ var o2 = {};
+ items.getRangeAt(i, o1, o2);
+ var min = o1.value;
+ var max = o2.value;
+ for (let j = min; j <= max; j++) {
+ if (ca_tab.selected) {
+ tree_item = caTreeView.getTreeItem(j);
+ } else if (mine_tab.selected) {
+ tree_item = userTreeView.getTreeItem(j);
+ } else if (others_tab.selected) {
+ tree_item = emailTreeView.getTreeItem(j);
+ }
+ if (tree_item) {
+ var sc = selected_tree_items.length;
+ selected_tree_items[sc] = tree_item;
+ selected_index[sc] = j;
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Returns true if nothing in the given cert tree is selected or if the
+ * selection includes a container. Returns false otherwise.
+ *
+ * @param {nsICertTree} certTree
+ * @returns {boolean}
+ */
+function nothingOrContainerSelected(certTree) {
+ var certTreeSelection = certTree.selection;
+ var numSelectionRanges = certTreeSelection.getRangeCount();
+
+ if (numSelectionRanges == 0) {
+ return true;
+ }
+
+ for (var i = 0; i < numSelectionRanges; i++) {
+ var o1 = {};
+ var o2 = {};
+ certTreeSelection.getRangeAt(i, o1, o2);
+ var minIndex = o1.value;
+ var maxIndex = o2.value;
+ for (var j = minIndex; j <= maxIndex; j++) {
+ if (certTree.isContainer(j)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+async function promptError(aErrorCode) {
+ if (aErrorCode != Ci.nsIX509CertDB.Success) {
+ let msgName = "pkcs12-unknown-err";
+ switch (aErrorCode) {
+ case Ci.nsIX509CertDB.ERROR_PKCS12_NOSMARTCARD_EXPORT:
+ msgName = "pkcs12-info-no-smartcard-backup";
+ break;
+ case Ci.nsIX509CertDB.ERROR_PKCS12_RESTORE_FAILED:
+ msgName = "pkcs12-unknown-err-restore";
+ break;
+ case Ci.nsIX509CertDB.ERROR_PKCS12_BACKUP_FAILED:
+ msgName = "pkcs12-unknown-err-backup";
+ break;
+ case Ci.nsIX509CertDB.ERROR_PKCS12_CERT_COLLISION:
+ case Ci.nsIX509CertDB.ERROR_PKCS12_DUPLICATE_DATA:
+ msgName = "pkcs12-dup-data";
+ break;
+ case Ci.nsIX509CertDB.ERROR_BAD_PASSWORD:
+ msgName = "pk11-bad-password";
+ break;
+ case Ci.nsIX509CertDB.ERROR_DECODE_ERROR:
+ msgName = "pkcs12-decode-err";
+ break;
+ default:
+ break;
+ }
+ let [message] = await document.l10n.formatValues([{ id: msgName }]);
+ let prompter = Services.ww.getNewPrompter(window);
+ prompter.alert(null, message);
+ }
+}
+
+/**
+ * Enables or disables buttons corresponding to a cert tree depending on what
+ * is selected in the cert tree.
+ *
+ * @param {nsICertTree} certTree
+ * @param {Array} idList A list of string identifiers for button elements to
+ * enable or disable.
+ */
+function enableButtonsForCertTree(certTree, idList) {
+ let disableButtons = nothingOrContainerSelected(certTree);
+
+ for (let id of idList) {
+ document.getElementById(id).setAttribute("disabled", disableButtons);
+ }
+}
+
+function ca_enableButtons() {
+ let idList = [
+ "ca_viewButton",
+ "ca_editButton",
+ "ca_exportButton",
+ "ca_deleteButton",
+ ];
+ enableButtonsForCertTree(caTreeView, idList);
+}
+
+function mine_enableButtons() {
+ let idList = ["mine_viewButton", "mine_backupButton", "mine_deleteButton"];
+ enableButtonsForCertTree(userTreeView, idList);
+}
+
+function email_enableButtons() {
+ let idList = ["email_viewButton", "email_exportButton", "email_deleteButton"];
+ enableButtonsForCertTree(emailTreeView, idList);
+}
+
+async function backupCerts() {
+ getSelectedCerts();
+ var numcerts = selected_certs.length;
+ if (numcerts == 0) {
+ return;
+ }
+
+ var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let [backupFileDialog, filePkcs12Spec] = await document.l10n.formatValues([
+ { id: "choose-p12-backup-file-dialog" },
+ { id: "file-browse-pkcs12-spec" },
+ ]);
+ fp.init(window, backupFileDialog, Ci.nsIFilePicker.modeSave);
+ fp.appendFilter(filePkcs12Spec, "*.p12");
+ fp.appendFilters(Ci.nsIFilePicker.filterAll);
+ fp.defaultExtension = "p12";
+ fp.open(rv => {
+ if (
+ rv == Ci.nsIFilePicker.returnOK ||
+ rv == Ci.nsIFilePicker.returnReplace
+ ) {
+ let password = {};
+ if (certdialogs.setPKCS12FilePassword(window, password)) {
+ let errorCode = certdb.exportPKCS12File(
+ fp.file,
+ selected_certs,
+ password.value
+ );
+ promptError(errorCode);
+ }
+ }
+ });
+}
+
+function backupAllCerts() {
+ // Select all rows, then call doBackup()
+ userTreeView.selection.selectAll();
+ backupCerts();
+}
+
+function editCerts() {
+ getSelectedCerts();
+
+ for (let cert of selected_certs) {
+ window.browsingContext.topChromeWindow.openDialog(
+ "chrome://pippki/content/editcacert.xhtml",
+ "",
+ "chrome,centerscreen,modal",
+ cert
+ );
+ }
+}
+
+async function restoreCerts() {
+ var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let [
+ restoreFileDialog,
+ filePkcs12Spec,
+ fileCertSpec,
+ ] = await document.l10n.formatValues([
+ { id: "choose-p12-restore-file-dialog" },
+ { id: "file-browse-pkcs12-spec" },
+ { id: "file-browse-certificate-spec" },
+ ]);
+ fp.init(window, restoreFileDialog, Ci.nsIFilePicker.modeOpen);
+ fp.appendFilter(filePkcs12Spec, "*.p12; *.pfx");
+ fp.appendFilter(fileCertSpec, gCertFileTypes);
+ fp.appendFilters(Ci.nsIFilePicker.filterAll);
+ fp.open(rv => {
+ if (rv != Ci.nsIFilePicker.returnOK) {
+ return;
+ }
+
+ // If this is an X509 user certificate, import it as one.
+
+ var isX509FileType = false;
+ var fileTypesList = gCertFileTypes.slice(1).split("; *");
+ for (var type of fileTypesList) {
+ if (fp.file.path.endsWith(type)) {
+ isX509FileType = true;
+ break;
+ }
+ }
+
+ if (isX509FileType) {
+ let fstream = Cc[
+ "@mozilla.org/network/file-input-stream;1"
+ ].createInstance(Ci.nsIFileInputStream);
+ fstream.init(fp.file, -1, 0, 0);
+ let dataString = NetUtil.readInputStreamToString(
+ fstream,
+ fstream.available()
+ );
+ let dataArray = [];
+ for (let i = 0; i < dataString.length; i++) {
+ dataArray.push(dataString.charCodeAt(i));
+ }
+ fstream.close();
+ let prompter = Services.ww.getNewPrompter(window);
+ let interfaceRequestor = {
+ getInterface() {
+ return prompter;
+ },
+ };
+ certdb.importUserCertificate(
+ dataArray,
+ dataArray.length,
+ interfaceRequestor
+ );
+ } else {
+ // Otherwise, assume it's a PKCS12 file and import it that way.
+ let password = {};
+ let errorCode = Ci.nsIX509CertDB.ERROR_BAD_PASSWORD;
+ while (
+ errorCode == Ci.nsIX509CertDB.ERROR_BAD_PASSWORD &&
+ certdialogs.getPKCS12FilePassword(window, password)
+ ) {
+ errorCode = certdb.importPKCS12File(fp.file, password.value);
+ if (
+ errorCode == Ci.nsIX509CertDB.ERROR_BAD_PASSWORD &&
+ !password.value.length
+ ) {
+ // It didn't like empty string password, try no password.
+ errorCode = certdb.importPKCS12File(fp.file, null);
+ }
+ promptError(errorCode);
+ }
+ }
+
+ var certcache = certdb.getCerts();
+ userTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.USER_CERT);
+ userTreeView.selection.clearSelection();
+ caTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.CA_CERT);
+ caTreeView.selection.clearSelection();
+ enableBackupAllButton();
+ });
+}
+
+async function exportCerts() {
+ getSelectedCerts();
+
+ for (let cert of selected_certs) {
+ await exportToFile(window, cert);
+ }
+}
+
+/**
+ * Deletes the selected certs in the active tab.
+ */
+function deleteCerts() {
+ getSelectedTreeItems();
+ let numcerts = selected_tree_items.length;
+ if (numcerts == 0) {
+ return;
+ }
+
+ const treeViewMap = {
+ mine_tab: userTreeView,
+ ca_tab: caTreeView,
+ others_tab: emailTreeView,
+ };
+ let selTab = document.getElementById("certMgrTabbox").selectedItem;
+ let selTabID = selTab.getAttribute("id");
+
+ if (!(selTabID in treeViewMap)) {
+ return;
+ }
+
+ let retVals = {
+ deleteConfirmed: false,
+ };
+ window.browsingContext.topChromeWindow.openDialog(
+ "chrome://pippki/content/deletecert.xhtml",
+ "",
+ "chrome,centerscreen,modal",
+ selTabID,
+ selected_tree_items,
+ retVals
+ );
+
+ if (retVals.deleteConfirmed) {
+ let treeView = treeViewMap[selTabID];
+
+ for (let t = numcerts - 1; t >= 0; t--) {
+ treeView.deleteEntryObject(selected_index[t]);
+ }
+
+ selected_tree_items = [];
+ selected_index = [];
+ treeView.selection.clearSelection();
+ if (selTabID == "mine_tab") {
+ enableBackupAllButton();
+ }
+ }
+}
+
+function viewCerts() {
+ getSelectedCerts();
+
+ for (let cert of selected_certs) {
+ viewCertHelper(window, cert);
+ }
+}
+
+async function addCACerts() {
+ var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let [importCa, fileCertSpec] = await document.l10n.formatValues([
+ { id: "import-ca-certs-prompt" },
+ { id: "file-browse-certificate-spec" },
+ ]);
+ fp.init(window, importCa, Ci.nsIFilePicker.modeOpen);
+ fp.appendFilter(fileCertSpec, gCertFileTypes);
+ fp.appendFilters(Ci.nsIFilePicker.filterAll);
+ fp.open(rv => {
+ if (rv == Ci.nsIFilePicker.returnOK) {
+ certdb.importCertsFromFile(fp.file, Ci.nsIX509Cert.CA_CERT);
+ let certcache = certdb.getCerts();
+ caTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.CA_CERT);
+ caTreeView.selection.clearSelection();
+ }
+ });
+}
+
+async function addEmailCert() {
+ var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let [importEmail, fileCertSpec] = await document.l10n.formatValues([
+ { id: "import-email-cert-prompt" },
+ { id: "file-browse-certificate-spec" },
+ ]);
+ fp.init(window, importEmail, Ci.nsIFilePicker.modeOpen);
+ fp.appendFilter(fileCertSpec, gCertFileTypes);
+ fp.appendFilters(Ci.nsIFilePicker.filterAll);
+ fp.open(rv => {
+ if (rv == Ci.nsIFilePicker.returnOK) {
+ certdb.importCertsFromFile(fp.file, Ci.nsIX509Cert.EMAIL_CERT);
+ var certcache = certdb.getCerts();
+ emailTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.EMAIL_CERT);
+ emailTreeView.selection.clearSelection();
+ caTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.CA_CERT);
+ caTreeView.selection.clearSelection();
+ }
+ });
+}
diff --git a/security/manager/pki/resources/content/certManager.xhtml b/security/manager/pki/resources/content/certManager.xhtml
new file mode 100644
index 0000000000..6e90b57b1d
--- /dev/null
+++ b/security/manager/pki/resources/content/certManager.xhtml
@@ -0,0 +1,225 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://pippki/content/certManager.css" type="text/css"?>
+
+<!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();"
+ 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"/>
+
+ <tabbox id="certmanagertabs" flex="1" 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"/>
+ <tree id="user-tree" flex="1" enableColumnDrag="true"
+ onselect="mine_enableButtons()">
+ <treecols>
+ <!--
+ The below code may suggest that 'ordinal' is still a supported XUL
+ XUL attribute. It is not. This is a crutch so that we can
+ continue persisting the CSS -moz-box-ordinal-group attribute,
+ which is the appropriate replacement for the ordinal attribute
+ but cannot yet be easily persisted. The code that synchronizes
+ the attribute with the CSS lives in
+ toolkit/content/widget/tree.js and is specific to tree elements.
+ -->
+ <treecol id="certcol" data-l10n-id="certmgr-cert-name" primary="true"
+ persist="hidden width ordinal" flex="1"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="tokencol" data-l10n-id="certmgr-token-name"
+ persist="hidden width ordinal" flex="1"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="serialnumcol" data-l10n-id="certmgr-serial"
+ persist="hidden width ordinal" flex="1"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="issuedcol" data-l10n-id="certmgr-begins-label"
+ hidden="true" persist="hidden width ordinal" flex="1"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="expiredcol" data-l10n-id="certmgr-expires-label"
+ persist="hidden width ordinal" flex="1"/>
+ </treecols>
+ <treechildren ondblclick="viewCerts();"/>
+ </tree>
+
+ <separator class="thin"/>
+
+ <hbox>
+ <button id="mine_viewButton" class="normal"
+ data-l10n-id="certmgr-view"
+ disabled="true" oncommand="viewCerts();"/>
+ <button id="mine_backupButton" class="normal"
+ data-l10n-id="certmgr-backup"
+ disabled="true" oncommand="backupCerts();"/>
+ <button id="mine_backupAllButton" class="normal"
+ data-l10n-id="certmgr-backup-all"
+ oncommand="backupAllCerts();"/>
+ <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"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="emailcol" data-l10n-id="certmgr-email"
+ flex="1"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="expiredcol" data-l10n-id="certmgr-expires-label"
+ flex="1"/>
+ </treecols>
+ <treechildren flex="1" ondblclick="viewCerts();"/>
+ </tree>
+
+ <separator class="thin"/>
+
+ <hbox>
+ <button id="email_viewButton"
+ data-l10n-id="certmgr-view"
+ disabled="true" oncommand="viewCerts();"/>
+ <button id="email_addButton"
+ data-l10n-id="certmgr-restore"
+ oncommand="addEmailCert();"/>
+ <button id="email_exportButton"
+ data-l10n-id="certmgr-export"
+ disabled="true" oncommand="exportCerts();"/>
+ <button id="email_deleteButton"
+ data-l10n-id="certmgr-delete"
+ disabled="true" oncommand="deleteCerts();"/>
+ </hbox>
+ </vbox>
+
+ <vbox id="webCerts" flex="1">
+ <description data-l10n-id="certmgr-server"></description>
+ <separator class="thin"/>
+
+ <listheader equalsize="always">
+ <treecol id="sitecol" data-l10n-id="certmgr-cert-server" primary="true" flex="1"/>
+ <treecol id="certcol" data-l10n-id="certmgr-cert-name" flex="1"/>
+ <treecol id="lifetimecol" data-l10n-id="certmgr-override-lifetime" flex="1"/>
+ </listheader>
+ <richlistbox ondblclick="serverRichList.viewSelectedRichListItem();" id="serverList" flex="1" selected="false"/>
+
+ <separator class="thin"/>
+
+ <hbox>
+ <button id="websites_viewButton"
+ data-l10n-id="certmgr-view" oncommand="serverRichList.viewSelectedRichListItem();"/>
+ <button id="websites_exportButton"
+ data-l10n-id="certmgr-export" oncommand="serverRichList.exportSelectedRichListItem();"/>
+ <button id="websites_deleteButton"
+ data-l10n-id="certmgr-delete" oncommand="serverRichList.deleteSelectedRichListItem();"/>
+ <button id="websites_exceptionButton"
+ data-l10n-id="certmgr-add-exception"
+ oncommand="serverRichList.addException();"/>
+ </hbox>
+ </vbox>
+ <vbox id="CACerts" flex="1">
+ <description data-l10n-id="certmgr-ca"></description>
+ <separator class="thin"/>
+ <tree id="ca-tree" flex="1" enableColumnDrag="true"
+ onselect="ca_enableButtons()">
+ <treecols>
+ <!--
+ The below code may suggest that 'ordinal' is still a supported XUL
+ XUL attribute. It is not. This is a crutch so that we can
+ continue persisting the CSS -moz-box-ordinal-group attribute,
+ which is the appropriate replacement for the ordinal attribute
+ but cannot yet be easily persisted. The code that synchronizes
+ the attribute with the CSS lives in
+ toolkit/content/widget/tree.js and is specific to tree elements.
+ -->
+ <treecol id="certcol" data-l10n-id="certmgr-cert-name" primary="true"
+ persist="hidden width ordinal" flex="1"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="tokencol" data-l10n-id="certmgr-token-name"
+ persist="hidden width ordinal" flex="1"/>
+ </treecols>
+ <treechildren ondblclick="viewCerts();"/>
+ </tree>
+
+ <separator class="thin"/>
+
+ <hbox>
+ <button id="ca_viewButton"
+ data-l10n-id="certmgr-view"
+ disabled="true" oncommand="viewCerts();"/>
+ <button id="ca_editButton"
+ data-l10n-id="certmgr-edit"
+ disabled="true" oncommand="editCerts();"/>
+ <button id="ca_addButton"
+ data-l10n-id="certmgr-restore"
+ oncommand="addCACerts();"/>
+ <button id="ca_exportButton"
+ data-l10n-id="certmgr-export"
+ disabled="true" oncommand="exportCerts();"/>
+ <button id="ca_deleteButton"
+ data-l10n-id="certmgr-delete-builtin"
+ disabled="true" oncommand="deleteCerts();"/>
+ </hbox>
+ </vbox>
+ </tabpanels>
+ </tabbox>
+</dialog>
+</window>
diff --git a/security/manager/pki/resources/content/changepassword.js b/security/manager/pki/resources/content/changepassword.js
new file mode 100644
index 0000000000..e3d6b118bf
--- /dev/null
+++ b/security/manager/pki/resources/content/changepassword.js
@@ -0,0 +1,212 @@
+/* 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/. */
+"use strict";
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+XPCOMUtils.defineLazyGetter(
+ this,
+ "l10n",
+ () => new Localization(["security/pippki/pippki.ftl"], true)
+);
+
+var params;
+var token;
+var pw1;
+
+function doPrompt(messageL10nId) {
+ let msg = l10n.formatValueSync(messageL10nId);
+ Services.prompt.alert(window, null, msg);
+}
+
+function onLoad() {
+ document.getElementById("set_password").getButton("accept").disabled = true;
+ document.addEventListener("dialogaccept", setPassword);
+
+ pw1 = document.getElementById("pw1");
+ params = window.arguments[0].QueryInterface(Ci.nsIDialogParamBlock);
+ token = params.objects.GetElementAt(0).QueryInterface(Ci.nsIPK11Token);
+
+ document.l10n.setAttributes(
+ document.getElementById("tokenName"),
+ "change-password-token",
+ { tokenName: token.tokenName }
+ );
+ process();
+}
+
+function process() {
+ let bundle = document.getElementById("pippki_bundle");
+ let oldpwbox = document.getElementById("oldpw");
+ let msgBox = document.getElementById("message");
+ // If the token is unitialized, don't use the old password box.
+ // Otherwise, do.
+ if ((token.needsLogin() && token.needsUserInit) || !token.needsLogin()) {
+ oldpwbox.hidden = true;
+ msgBox.setAttribute("value", bundle.getString("password_not_set"));
+ msgBox.hidden = false;
+
+ if (!token.needsLogin()) {
+ oldpwbox.setAttribute("inited", "empty");
+ } else {
+ oldpwbox.setAttribute("inited", "true");
+ }
+
+ // Select first password field
+ document.getElementById("pw1").focus();
+ } else {
+ // Select old password field
+ oldpwbox.hidden = false;
+ msgBox.hidden = true;
+ oldpwbox.setAttribute("inited", "false");
+ oldpwbox.focus();
+ }
+
+ // Return value 0 means "canceled"
+ params.SetInt(1, 0);
+
+ checkPasswords();
+}
+
+function setPassword(event) {
+ var oldpwbox = document.getElementById("oldpw");
+ var initpw = oldpwbox.getAttribute("inited");
+
+ var success = false;
+
+ if (initpw == "false" || initpw == "empty") {
+ try {
+ var oldpw = "";
+ var passok = 0;
+
+ if (initpw == "empty") {
+ passok = 1;
+ } else {
+ oldpw = oldpwbox.value;
+ passok = token.checkPassword(oldpw);
+ }
+
+ if (passok) {
+ if (initpw == "empty" && pw1.value == "") {
+ // checkPasswords() should have prevented this path from being reached.
+ } else {
+ if (pw1.value == "") {
+ var secmoddb = Cc[
+ "@mozilla.org/security/pkcs11moduledb;1"
+ ].getService(Ci.nsIPKCS11ModuleDB);
+ if (secmoddb.isFIPSEnabled) {
+ // empty passwords are not allowed in FIPS mode
+ doPrompt("pippki-pw-change2empty-in-fips-mode");
+ passok = 0;
+ }
+ }
+ if (passok) {
+ token.changePassword(oldpw, pw1.value);
+ if (pw1.value == "") {
+ doPrompt("pippki-pw-erased-ok");
+ } else {
+ doPrompt("pippki-pw-change-ok");
+ }
+ success = true;
+ }
+ }
+ } else {
+ oldpwbox.focus();
+ oldpwbox.setAttribute("value", "");
+ doPrompt("pippki-incorrect-pw");
+ }
+ } catch (e) {
+ doPrompt("pippki-failed-pw-change");
+ }
+ } else {
+ token.initPassword(pw1.value);
+ if (pw1.value == "") {
+ doPrompt("pippki-pw-not-wanted");
+ }
+ success = true;
+ }
+
+ if (success && params) {
+ // Return value 1 means "successfully executed ok"
+ params.SetInt(1, 1);
+ }
+
+ // Terminate dialog
+ if (!success) {
+ event.preventDefault();
+ }
+}
+
+function setPasswordStrength() {
+ // We weigh the quality of the password by checking the number of:
+ // - Characters
+ // - Numbers
+ // - Non-alphanumeric chars
+ // - Upper and lower case characters
+
+ let pw = document.getElementById("pw1").value;
+
+ let pwlength = pw.length;
+ if (pwlength > 5) {
+ pwlength = 5;
+ }
+
+ let numnumeric = pw.replace(/[0-9]/g, "");
+ let numeric = pw.length - numnumeric.length;
+ if (numeric > 3) {
+ numeric = 3;
+ }
+
+ let symbols = pw.replace(/\W/g, "");
+ let numsymbols = pw.length - symbols.length;
+ if (numsymbols > 3) {
+ numsymbols = 3;
+ }
+
+ let numupper = pw.replace(/[A-Z]/g, "");
+ let upper = pw.length - numupper.length;
+ if (upper > 3) {
+ upper = 3;
+ }
+
+ let pwstrength =
+ pwlength * 10 - 20 + numeric * 10 + numsymbols * 15 + upper * 10;
+
+ // Clamp strength to [0, 100].
+ if (pwstrength < 0) {
+ pwstrength = 0;
+ }
+ if (pwstrength > 100) {
+ pwstrength = 100;
+ }
+
+ let meter = document.getElementById("pwmeter");
+ meter.setAttribute("value", pwstrength);
+}
+
+function checkPasswords() {
+ let pw1 = document.getElementById("pw1").value;
+ let pw2 = document.getElementById("pw2").value;
+
+ var oldpwbox = document.getElementById("oldpw");
+ if (oldpwbox) {
+ var initpw = oldpwbox.getAttribute("inited");
+
+ if (initpw == "empty" && pw1 == "") {
+ // The token has already been initialized, therefore this dialog
+ // was called with the intention to change the password.
+ // The token currently uses an empty password.
+ // We will not allow changing the password from empty to empty.
+ document
+ .getElementById("set_password")
+ .getButton("accept").disabled = true;
+ return;
+ }
+ }
+
+ document.getElementById("set_password").getButton("accept").disabled =
+ pw1 != pw2;
+}
diff --git a/security/manager/pki/resources/content/changepassword.xhtml b/security/manager/pki/resources/content/changepassword.xhtml
new file mode 100644
index 0000000000..850fb01108
--- /dev/null
+++ b/security/manager/pki/resources/content/changepassword.xhtml
@@ -0,0 +1,65 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+<window
+ data-l10n-id="change-device-password-window"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="onLoad();">
+<dialog id="set_password"
+ buttons="accept,cancel">
+
+<linkset>
+ <html:link rel="localization" href="security/pippki/pippki.ftl"/>
+</linkset>
+
+<stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/>
+
+<script src="chrome://global/content/globalOverlay.js"/>
+<script src="chrome://global/content/editMenuOverlay.js"/>
+
+<script src="chrome://pippki/content/changepassword.js"/>
+
+<hbox align="center">
+ <label id="tokenName" data-l10n-id="change-password-token" data-l10n-args='{"tokenName":""}'/>
+</hbox>
+
+<separator/>
+
+<vbox>
+ <hbox class="input-row">
+ <label flex="1" data-l10n-id="change-password-old" />
+ <html:input id="oldpw" type="password"/>
+ <!-- This textbox is inserted as a workaround to the fact that making the 'type'
+ & 'disabled' property of the 'oldpw' textbox toggle between ['password' &
+ 'false'] and ['text' & 'true'] - as would be necessary if the menu has more
+ than one tokens, some initialized and some not - does not work properly. So,
+ either the textbox 'oldpw' or the textbox 'message' would be displayed,
+ depending on the state of the token selected
+ -->
+ <html:input id="message" disabled="true" />
+ </hbox>
+ <hbox class="input-row">
+ <label flex="1" data-l10n-id="change-password-new" />
+ <html:input id="pw1" type="password"
+ oninput="setPasswordStrength(); checkPasswords();"/>
+ </hbox>
+ <hbox class="input-row">
+ <label flex="1" data-l10n-id="change-password-reenter" />
+ <html:input id="pw2" type="password" oninput="checkPasswords();"/>
+ </hbox>
+</vbox>
+
+<vbox style="margin: 6px;">
+ <label for="pwmeter" style="display: -moz-box;" data-l10n-id="password-quality-meter" />
+ <html:progress id="pwmeter" value="0" max="100"/>
+</vbox>
+
+</dialog>
+</window>
diff --git a/security/manager/pki/resources/content/clientauthask.js b/security/manager/pki/resources/content/clientauthask.js
new file mode 100644
index 0000000000..da9fc07abc
--- /dev/null
+++ b/security/manager/pki/resources/content/clientauthask.js
@@ -0,0 +1,200 @@
+/* -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 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/. */
+/* import-globals-from pippki.js */
+"use strict";
+
+const { asn1js } = ChromeUtils.import(
+ "chrome://global/content/certviewer/asn1js_bundle.jsm"
+);
+const { pkijs } = ChromeUtils.import(
+ "chrome://global/content/certviewer/pkijs_bundle.jsm"
+);
+const { pvutils } = ChromeUtils.import(
+ "chrome://global/content/certviewer/pvutils_bundle.jsm"
+);
+
+const { Integer, fromBER } = asn1js.asn1js;
+const { Certificate } = pkijs.pkijs;
+const { fromBase64, stringToArrayBuffer } = pvutils.pvutils;
+
+const { certDecoderInitializer } = ChromeUtils.import(
+ "chrome://global/content/certviewer/certDecoder.jsm"
+);
+const { parse, pemToDER } = certDecoderInitializer(
+ Integer,
+ fromBER,
+ Certificate,
+ fromBase64,
+ stringToArrayBuffer,
+ crypto
+);
+
+/**
+ * @file Implements the functionality of clientauthask.xhtml: a dialog that allows
+ * a user pick a client certificate for TLS client authentication.
+ * @param {string} window.arguments.0
+ * The hostname of the server requesting client authentication.
+ * @param {string} window.arguments.1
+ * The Organization of the server cert.
+ * @param {string} window.arguments.2
+ * The Organization of the issuer of the server cert.
+ * @param {number} window.arguments.3
+ * The port of the server.
+ * @param {nsISupports} window.arguments.4
+ * List of certificates the user can choose from, queryable to
+ * nsIArray<nsIX509Cert>.
+ * @param {nsISupports} window.arguments.5
+ * Object to set the return values of calling the dialog on, queryable
+ * to the underlying type of ClientAuthAskReturnValues.
+ */
+
+/**
+ * @typedef ClientAuthAskReturnValues
+ * @type {nsIWritablePropertyBag2}
+ * @property {boolean} certChosen
+ * Set to true if the user chose a cert and accepted the dialog, false
+ * otherwise.
+ * @property {boolean} rememberSelection
+ * Set to true if the user wanted their cert selection to be
+ * remembered, false otherwise.
+ * @property {number} selectedIndex
+ * The index the chosen cert is at for the given cert list. Undefined
+ * value if |certChosen| is not true.
+ */
+
+/**
+ * The pippki <stringbundle> element.
+ *
+ * @type {stringbundle}
+ * @see {@link toolkit/content/widgets/stringbundle.js}
+ */
+var bundle;
+/**
+ * The array of certs the user can choose from.
+ *
+ * @type {nsIArray<nsIX509Cert>}
+ */
+var certArray;
+/**
+ * The checkbox storing whether the user wants to remember the selected cert.
+ *
+ * @type {HTMLInputElement} Element checkbox, has to have |checked| property.
+ */
+var rememberBox;
+
+async function onLoad() {
+ bundle = document.getElementById("pippki_bundle");
+ let rememberSetting = Services.prefs.getBoolPref(
+ "security.remember_cert_checkbox_default_setting"
+ );
+
+ rememberBox = document.getElementById("rememberBox");
+ rememberBox.label = bundle.getString("clientAuthRemember");
+ rememberBox.checked = rememberSetting;
+
+ let hostname = window.arguments[0];
+ let org = window.arguments[1];
+ let issuerOrg = window.arguments[2];
+ let port = window.arguments[3];
+ let formattedOrg = bundle.getFormattedString("clientAuthMessage1", [org]);
+ let formattedIssuerOrg = bundle.getFormattedString("clientAuthMessage2", [
+ issuerOrg,
+ ]);
+ let formattedHostnameAndPort = bundle.getFormattedString(
+ "clientAuthHostnameAndPort",
+ [hostname, port.toString()]
+ );
+ setText("hostname", formattedHostnameAndPort);
+ setText("organization", formattedOrg);
+ setText("issuer", formattedIssuerOrg);
+
+ let selectElement = document.getElementById("nicknames");
+ certArray = window.arguments[4].QueryInterface(Ci.nsIArray);
+ for (let i = 0; i < certArray.length; i++) {
+ let menuItemNode = document.createXULElement("menuitem");
+ let cert = certArray.queryElementAt(i, Ci.nsIX509Cert);
+ let nickAndSerial = bundle.getFormattedString("clientAuthNickAndSerial", [
+ cert.displayName,
+ cert.serialNumber,
+ ]);
+ menuItemNode.setAttribute("value", i);
+ menuItemNode.setAttribute("label", nickAndSerial); // This is displayed.
+ selectElement.menupopup.appendChild(menuItemNode);
+ if (i == 0) {
+ selectElement.selectedItem = menuItemNode;
+ }
+ }
+
+ await setDetails();
+ document.addEventListener("dialogaccept", doOK);
+ document.addEventListener("dialogcancel", doCancel);
+
+ Services.obs.notifyObservers(
+ document.getElementById("certAuthAsk"),
+ "cert-dialog-loaded"
+ );
+}
+
+/**
+ * Populates the details section with information concerning the selected cert.
+ */
+async function setDetails() {
+ let index = parseInt(document.getElementById("nicknames").value);
+ let cert = certArray.queryElementAt(index, Ci.nsIX509Cert);
+
+ const formatter = new Intl.DateTimeFormat(undefined, {
+ dateStyle: "medium",
+ timeStyle: "long",
+ });
+ let detailLines = [
+ bundle.getFormattedString("clientAuthIssuedTo", [cert.subjectName]),
+ bundle.getFormattedString("clientAuthSerial", [cert.serialNumber]),
+ bundle.getFormattedString("clientAuthValidityPeriod", [
+ formatter.format(new Date(cert.validity.notBefore / 1000)),
+ formatter.format(new Date(cert.validity.notAfter / 1000)),
+ ]),
+ ];
+ let parsedCert = await parse(pemToDER(cert.getBase64DERString()));
+ let keyUsages = parsedCert.ext.keyUsages;
+ if (keyUsages && keyUsages.purposes.length) {
+ detailLines.push(
+ bundle.getFormattedString("clientAuthKeyUsages", [keyUsages.purposes])
+ );
+ }
+ let emailAddresses = cert.getEmailAddresses();
+ if (emailAddresses.length) {
+ let joinedAddresses = emailAddresses.join(", ");
+ detailLines.push(
+ bundle.getFormattedString("clientAuthEmailAddresses", [joinedAddresses])
+ );
+ }
+ detailLines.push(
+ bundle.getFormattedString("clientAuthIssuedBy", [cert.issuerName])
+ );
+ detailLines.push(
+ bundle.getFormattedString("clientAuthStoredOn", [cert.tokenName])
+ );
+
+ document.getElementById("details").value = detailLines.join("\n");
+}
+
+async function onCertSelected() {
+ await setDetails();
+}
+
+function doOK() {
+ let retVals = window.arguments[5].QueryInterface(Ci.nsIWritablePropertyBag2);
+ retVals.setPropertyAsBool("certChosen", true);
+ let index = parseInt(document.getElementById("nicknames").value);
+ retVals.setPropertyAsUint32("selectedIndex", index);
+ retVals.setPropertyAsBool("rememberSelection", rememberBox.checked);
+}
+
+function doCancel() {
+ let retVals = window.arguments[5].QueryInterface(Ci.nsIWritablePropertyBag2);
+ retVals.setPropertyAsBool("certChosen", false);
+ retVals.setPropertyAsBool("rememberSelection", rememberBox.checked);
+}
diff --git a/security/manager/pki/resources/content/clientauthask.xhtml b/security/manager/pki/resources/content/clientauthask.xhtml
new file mode 100644
index 0000000000..94645e7ba0
--- /dev/null
+++ b/security/manager/pki/resources/content/clientauthask.xhtml
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+<window data-l10n-id="client-auth-window"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="onLoad();">
+<dialog id="certAuthAsk"
+ buttons="accept,cancel">
+
+<linkset>
+ <html:link rel="localization" href="security/pippki/pippki.ftl"/>
+</linkset>
+
+<stringbundleset id="stringbundleset">
+ <stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/>
+</stringbundleset>
+
+<script src="chrome://pippki/content/pippki.js"/>
+<script src="chrome://pippki/content/clientauthask.js"/>
+<script src="chrome://global/content/globalOverlay.js"/>
+<script src="chrome://global/content/editMenuOverlay.js"/>
+
+<description style="font-weight: bold;" data-l10n-id="client-auth-site-description"></description>
+<description id="hostname"/>
+<description id="organization"/>
+<description id="issuer"/>
+
+<description style="font-weight: bold;" data-l10n-id="client-auth-choose-cert"></description>
+<!-- The items in this menulist must never be sorted,
+ but remain in the order filled by the application
+-->
+<menulist id="nicknames" oncommand="onCertSelected();" native="true">
+ <menupopup/>
+</menulist>
+<description data-l10n-id="client-auth-cert-details"></description>
+<html:textarea readonly="readonly" id="details" style="height: 11em;"/>
+<checkbox id="rememberBox" checked="true" native="true"/>
+
+</dialog>
+</window>
diff --git a/security/manager/pki/resources/content/deletecert.js b/security/manager/pki/resources/content/deletecert.js
new file mode 100644
index 0000000000..7d926863ed
--- /dev/null
+++ b/security/manager/pki/resources/content/deletecert.js
@@ -0,0 +1,121 @@
+/* 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/. */
+/* import-globals-from pippki.js */
+"use strict";
+
+/**
+ * @file Implements the functionality of deletecert.xhtml: a dialog that allows a
+ * user to confirm whether to delete certain certificates.
+ * @param {string} window.arguments.0
+ * One of the tab IDs listed in certManager.xhtml.
+ * @param {object[]} window.arguments.1
+ * An array of objects representing the certs to delete.
+ * Each must have a 'cert' property or a 'hostPort' property.
+ * @param {DeleteCertReturnValues} window.arguments.2
+ * Object holding the return values of calling the dialog.
+ */
+
+/**
+ * @typedef DeleteCertReturnValues
+ * @type {object}
+ * @property {boolean} deleteConfirmed
+ * Set to true if the user confirmed deletion of the given certs,
+ * false otherwise.
+ */
+
+/**
+ * Returns the element to represent the given cert to delete.
+ *
+ * @param {object} certToDelete
+ * The item to represent.
+ * @returns {Element}
+ * A element of each cert tree item.
+ */
+function getLabelForCertToDelete(certToDelete) {
+ let element = document.createXULElement("label");
+ let cert = certToDelete.cert;
+ if (!cert) {
+ element.setAttribute("value", certToDelete.hostPort);
+ return element;
+ }
+
+ const attributes = [
+ cert.commonName,
+ cert.organizationalUnit,
+ cert.organization,
+ cert.subjectName,
+ ];
+ for (let attribute of attributes) {
+ if (attribute) {
+ element.setAttribute("value", attribute);
+ return element;
+ }
+ }
+
+ document.l10n.setAttributes(element, "cert-with-serial", {
+ serialNumber: cert.serialNumber,
+ });
+ return element;
+}
+
+/**
+ * onload() handler.
+ */
+function onLoad() {
+ let typeFlag = window.arguments[0];
+ let confirm = document.getElementById("confirm");
+ let impact = document.getElementById("impact");
+ let prefixForType;
+ switch (typeFlag) {
+ case "mine_tab":
+ prefixForType = "delete-user-cert-";
+ break;
+ case "websites_tab":
+ prefixForType = "delete-ssl-override-";
+ break;
+ case "ca_tab":
+ prefixForType = "delete-ca-cert-";
+ break;
+ case "others_tab":
+ prefixForType = "delete-email-cert-";
+ break;
+ default:
+ return;
+ }
+
+ document.l10n.setAttributes(
+ document.documentElement,
+ prefixForType + "title"
+ );
+ document.l10n.setAttributes(confirm, prefixForType + "confirm");
+ document.l10n.setAttributes(impact, prefixForType + "impact");
+
+ document.addEventListener("dialogaccept", onDialogAccept);
+ document.addEventListener("dialogcancel", onDialogCancel);
+
+ let box = document.getElementById("certlist");
+ let certsToDelete = window.arguments[1];
+ for (let certToDelete of certsToDelete) {
+ let listItem = document.createXULElement("richlistitem");
+ let label = getLabelForCertToDelete(certToDelete);
+ listItem.appendChild(label);
+ box.appendChild(listItem);
+ }
+}
+
+/**
+ * ondialogaccept() handler.
+ */
+function onDialogAccept() {
+ let retVals = window.arguments[2];
+ retVals.deleteConfirmed = true;
+}
+
+/**
+ * ondialogcancel() handler.
+ */
+function onDialogCancel() {
+ let retVals = window.arguments[2];
+ retVals.deleteConfirmed = false;
+}
diff --git a/security/manager/pki/resources/content/deletecert.xhtml b/security/manager/pki/resources/content/deletecert.xhtml
new file mode 100644
index 0000000000..fffff3c27d
--- /dev/null
+++ b/security/manager/pki/resources/content/deletecert.xhtml
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+<window data-l10n-id="certmgr-delete-cert2"
+ data-l10n-attrs="style"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="onLoad();">
+<dialog id="deleteCertificate"
+ buttons="accept,cancel">
+
+ <linkset>
+ <html:link rel="localization" href="security/certificates/certManager.ftl"/>
+ </linkset>
+
+ <script src="pippki.js" />
+ <script src="chrome://pippki/content/deletecert.js"/>
+
+ <description id="confirm" style="width: 400px;"/>
+ <richlistbox id="certlist" class="box-padded" flex="1"
+ style="min-height: 8em; height: 8em; min-width: 35em;"/>
+ <description id="impact" style="width: 400px;"/>
+
+</dialog>
+</window>
diff --git a/security/manager/pki/resources/content/device_manager.js b/security/manager/pki/resources/content/device_manager.js
new file mode 100644
index 0000000000..deb58d2ff3
--- /dev/null
+++ b/security/manager/pki/resources/content/device_manager.js
@@ -0,0 +1,433 @@
+/* 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/. */
+"use strict";
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+var secmoddb;
+var skip_enable_buttons = false;
+
+/* Do the initial load of all PKCS# modules and list them. */
+function LoadModules() {
+ secmoddb = Cc["@mozilla.org/security/pkcs11moduledb;1"].getService(
+ Ci.nsIPKCS11ModuleDB
+ );
+ RefreshDeviceList();
+}
+
+async function doPrompt(l10n_id) {
+ let [msg] = await document.l10n.formatValues([{ id: l10n_id }]);
+ Services.prompt.alert(window, null, msg);
+}
+
+async function doConfirm(l10n_id) {
+ let [msg] = await document.l10n.formatValues([{ id: l10n_id }]);
+ return Services.prompt.confirm(window, null, msg);
+}
+
+function RefreshDeviceList() {
+ for (let module of secmoddb.listModules()) {
+ let slots = module.listSlots();
+ AddModule(module, slots);
+ }
+
+ // Set the text on the FIPS button.
+ SetFIPSButton();
+}
+
+function SetFIPSButton() {
+ var fipsButton = document.getElementById("fipsbutton");
+ if (secmoddb.isFIPSEnabled) {
+ document.l10n.setAttributes(fipsButton, "devmgr-button-disable-fips");
+ } else {
+ document.l10n.setAttributes(fipsButton, "devmgr-button-enable-fips");
+ }
+
+ var can_toggle = secmoddb.canToggleFIPS;
+ if (can_toggle) {
+ fipsButton.removeAttribute("disabled");
+ } else {
+ fipsButton.setAttribute("disabled", "true");
+ }
+}
+
+/* Add a module to the tree. slots is the array of slots in the module,
+ * to be represented as children.
+ */
+function AddModule(module, slots) {
+ var tree = document.getElementById("device_list");
+ var item = document.createXULElement("treeitem");
+ var row = document.createXULElement("treerow");
+ var cell = document.createXULElement("treecell");
+ cell.setAttribute("label", module.name);
+ row.appendChild(cell);
+ item.appendChild(row);
+ var parent = document.createXULElement("treechildren");
+ for (let slot of slots) {
+ var child_item = document.createXULElement("treeitem");
+ var child_row = document.createXULElement("treerow");
+ var child_cell = document.createXULElement("treecell");
+ child_cell.setAttribute("label", slot.name);
+ child_row.appendChild(child_cell);
+ child_item.appendChild(child_row);
+ child_item.setAttribute("pk11kind", "slot");
+ // 'slot' is an attribute on any HTML element, hence 'slotObject' instead.
+ child_item.slotObject = slot;
+ parent.appendChild(child_item);
+ }
+ item.appendChild(parent);
+ item.setAttribute("pk11kind", "module");
+ item.module = module;
+ item.setAttribute("open", "true");
+ item.setAttribute("container", "true");
+ tree.appendChild(item);
+}
+
+var selected_slot;
+var selected_module;
+
+/* get the slot selected by the user (can only be one-at-a-time) */
+function getSelectedItem() {
+ let tree = document.getElementById("device_tree");
+ if (tree.currentIndex < 0) {
+ return;
+ }
+ let item = tree.view.getItemAtIndex(tree.currentIndex);
+ selected_slot = null;
+ selected_module = null;
+ if (item) {
+ let kind = item.getAttribute("pk11kind");
+ if (kind == "slot") {
+ selected_slot = item.slotObject;
+ } else {
+ // (kind == "module")
+ selected_module = item.module;
+ }
+ }
+}
+
+function enableButtons() {
+ if (skip_enable_buttons) {
+ return;
+ }
+
+ var login_toggle = "true";
+ var logout_toggle = "true";
+ var pw_toggle = "true";
+ var unload_toggle = "true";
+ getSelectedItem();
+ if (selected_module) {
+ unload_toggle = "false";
+ showModuleInfo();
+ } else if (selected_slot) {
+ // here's the workaround - login functions are all with token,
+ // so grab the token type
+ var selected_token = selected_slot.getToken();
+ if (selected_token != null) {
+ if (selected_token.needsLogin() || !selected_token.needsUserInit) {
+ pw_toggle = "false";
+ if (selected_token.needsLogin()) {
+ if (selected_token.isLoggedIn()) {
+ logout_toggle = "false";
+ } else {
+ login_toggle = "false";
+ }
+ }
+ }
+
+ if (
+ !Services.policies.isAllowed("createMasterPassword") &&
+ selected_token.isInternalKeyToken &&
+ !selected_token.hasPassword
+ ) {
+ pw_toggle = "true";
+ }
+ }
+ showSlotInfo();
+ }
+ document
+ .getElementById("login_button")
+ .setAttribute("disabled", login_toggle);
+ document
+ .getElementById("logout_button")
+ .setAttribute("disabled", logout_toggle);
+ document
+ .getElementById("change_pw_button")
+ .setAttribute("disabled", pw_toggle);
+ document
+ .getElementById("unload_button")
+ .setAttribute("disabled", unload_toggle);
+}
+
+// clear the display of information for the slot
+function ClearInfoList() {
+ let infoList = document.getElementById("info_list");
+ while (infoList.hasChildNodes()) {
+ infoList.firstChild.remove();
+ }
+}
+
+function ClearDeviceList() {
+ ClearInfoList();
+
+ skip_enable_buttons = true;
+ var tree = document.getElementById("device_tree");
+ tree.view.selection.clearSelection();
+ skip_enable_buttons = false;
+
+ // Remove the existing listed modules so that a refresh doesn't display the
+ // module that just changed.
+ let deviceList = document.getElementById("device_list");
+ while (deviceList.hasChildNodes()) {
+ deviceList.firstChild.remove();
+ }
+}
+
+// show a list of info about a slot
+function showSlotInfo() {
+ var present = true;
+ ClearInfoList();
+ switch (selected_slot.status) {
+ case Ci.nsIPKCS11Slot.SLOT_DISABLED:
+ AddInfoRow(
+ "devinfo-status",
+ { l10nID: "devinfo-status-disabled" },
+ "tok_status"
+ );
+ present = false;
+ break;
+ case Ci.nsIPKCS11Slot.SLOT_NOT_PRESENT:
+ AddInfoRow(
+ "devinfo-status",
+ { l10nID: "devinfo-status-not-present" },
+ "tok_status"
+ );
+ present = false;
+ break;
+ case Ci.nsIPKCS11Slot.SLOT_UNINITIALIZED:
+ AddInfoRow(
+ "devinfo-status",
+ { l10nID: "devinfo-status-uninitialized" },
+ "tok_status"
+ );
+ break;
+ case Ci.nsIPKCS11Slot.SLOT_NOT_LOGGED_IN:
+ AddInfoRow(
+ "devinfo-status",
+ { l10nID: "devinfo-status-not-logged-in" },
+ "tok_status"
+ );
+ break;
+ case Ci.nsIPKCS11Slot.SLOT_LOGGED_IN:
+ AddInfoRow(
+ "devinfo-status",
+ { l10nID: "devinfo-status-logged-in" },
+ "tok_status"
+ );
+ break;
+ case Ci.nsIPKCS11Slot.SLOT_READY:
+ AddInfoRow(
+ "devinfo-status",
+ { l10nID: "devinfo-status-ready" },
+ "tok_status"
+ );
+ break;
+ default:
+ return;
+ }
+ AddInfoRow("devinfo-desc", { label: selected_slot.desc }, "slot_desc");
+ AddInfoRow("devinfo-man-id", { label: selected_slot.manID }, "slot_manID");
+ AddInfoRow(
+ "devinfo-hwversion",
+ { label: selected_slot.HWVersion },
+ "slot_hwv"
+ );
+ AddInfoRow(
+ "devinfo-fwversion",
+ { label: selected_slot.FWVersion },
+ "slot_fwv"
+ );
+ if (present) {
+ showTokenInfo();
+ }
+}
+
+function showModuleInfo() {
+ ClearInfoList();
+ AddInfoRow("devinfo-modname", { label: selected_module.name }, "module_name");
+ AddInfoRow(
+ "devinfo-modpath",
+ { label: selected_module.libName },
+ "module_path"
+ );
+}
+
+// add a row to the info list, as [col1 col2] (ex.: ["status" "logged in"])
+function AddInfoRow(l10nID, col2, cell_id) {
+ var tree = document.getElementById("info_list");
+ var item = document.createXULElement("treeitem");
+ var row = document.createXULElement("treerow");
+ var cell1 = document.createXULElement("treecell");
+ document.l10n.setAttributes(cell1, l10nID);
+ cell1.setAttribute("crop", "never");
+ row.appendChild(cell1);
+ var cell2 = document.createXULElement("treecell");
+ if (col2.l10nID) {
+ document.l10n.setAttributes(cell2, col2.l10nID);
+ } else {
+ cell2.setAttribute("label", col2.label);
+ }
+ cell2.setAttribute("crop", "never");
+ cell2.setAttribute("id", cell_id);
+ row.appendChild(cell2);
+ item.appendChild(row);
+ tree.appendChild(item);
+}
+
+// log in to a slot
+function doLogin() {
+ getSelectedItem();
+ // here's the workaround - login functions are with token
+ var selected_token = selected_slot.getToken();
+ try {
+ selected_token.login(false);
+ var tok_status = document.getElementById("tok_status");
+ if (selected_token.isLoggedIn()) {
+ document.l10n.setAttributes(tok_status, "devinfo-status-logged-in");
+ } else {
+ document.l10n.setAttributes(tok_status, "devinfo-status-not-logged-in");
+ }
+ } catch (e) {
+ doPrompt("login-failed");
+ }
+ enableButtons();
+}
+
+// log out of a slot
+function doLogout() {
+ getSelectedItem();
+ // here's the workaround - login functions are with token
+ var selected_token = selected_slot.getToken();
+ try {
+ selected_token.logoutAndDropAuthenticatedResources();
+ var tok_status = document.getElementById("tok_status");
+ if (selected_token.isLoggedIn()) {
+ document.l10n.setAttributes(tok_status, "devinfo-status-logged-in");
+ } else {
+ document.l10n.setAttributes(tok_status, "devinfo-status-not-logged-in");
+ }
+ } catch (e) {}
+ enableButtons();
+}
+
+// load a new device
+function doLoad() {
+ window.browsingContext.topChromeWindow.open(
+ "load_device.xhtml",
+ "loaddevice",
+ "chrome,centerscreen,modal"
+ );
+ ClearDeviceList();
+ RefreshDeviceList();
+}
+
+async function deleteSelected() {
+ getSelectedItem();
+ if (selected_module && (await doConfirm("del-module-warning"))) {
+ try {
+ secmoddb.deleteModule(selected_module.name);
+ } catch (e) {
+ doPrompt("del-module-error");
+ return false;
+ }
+ selected_module = null;
+ return true;
+ }
+ return false;
+}
+
+async function doUnload() {
+ if (await deleteSelected()) {
+ ClearDeviceList();
+ RefreshDeviceList();
+ }
+}
+
+function changePassword() {
+ getSelectedItem();
+ let params = Cc["@mozilla.org/embedcomp/dialogparam;1"].createInstance(
+ Ci.nsIDialogParamBlock
+ );
+ let objects = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+ objects.appendElement(selected_slot.getToken());
+ params.objects = objects;
+ window.browsingContext.topChromeWindow.openDialog(
+ "changepassword.xhtml",
+ "",
+ "chrome,centerscreen,modal",
+ params
+ );
+ showSlotInfo();
+ enableButtons();
+}
+
+// ------------------------------------- Old code
+
+function showTokenInfo() {
+ var selected_token = selected_slot.getToken();
+ AddInfoRow("devinfo-label", { label: selected_token.tokenName }, "tok_label");
+ AddInfoRow(
+ "devinfo-man-id",
+ { label: selected_token.tokenManID },
+ "tok_manID"
+ );
+ AddInfoRow(
+ "devinfo-serialnum",
+ { label: selected_token.tokenSerialNumber },
+ "tok_sNum"
+ );
+ AddInfoRow(
+ "devinfo-hwversion",
+ { label: selected_token.tokenHWVersion },
+ "tok_hwv"
+ );
+ AddInfoRow(
+ "devinfo-fwversion",
+ { label: selected_token.tokenFWVersion },
+ "tok_fwv"
+ );
+}
+
+function toggleFIPS() {
+ if (!secmoddb.isFIPSEnabled) {
+ // A restriction of FIPS mode is, the password must be set
+ // In FIPS mode the password must be non-empty.
+ // This is different from what we allow in NON-Fips mode.
+
+ var tokendb = Cc["@mozilla.org/security/pk11tokendb;1"].getService(
+ Ci.nsIPK11TokenDB
+ );
+ var internal_token = tokendb.getInternalKeyToken(); // nsIPK11Token
+ if (!internal_token.hasPassword) {
+ // Token has either no or an empty password.
+ doPrompt("fips-nonempty-primary-password-required");
+ return;
+ }
+ }
+
+ try {
+ secmoddb.toggleFIPSMode();
+ } catch (e) {
+ doPrompt("unable-to-toggle-fips");
+ return;
+ }
+
+ // Remove the existing listed modules so that a refresh doesn't display the
+ // module that just changed.
+ ClearDeviceList();
+
+ RefreshDeviceList();
+}
diff --git a/security/manager/pki/resources/content/device_manager.xhtml b/security/manager/pki/resources/content/device_manager.xhtml
new file mode 100644
index 0000000000..1c697d52e3
--- /dev/null
+++ b/security/manager/pki/resources/content/device_manager.xhtml
@@ -0,0 +1,70 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<!DOCTYPE dialog>
+
+<window windowtype="mozilla:devicemanager"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ data-l10n-id="devmgr-window"
+ data-l10n-attrs="style"
+ persist="screenX screenY width height"
+ onload="LoadModules();">
+<dialog id="devicemanager"
+ buttons="accept">
+
+<linkset>
+ <html:link rel="localization" href="security/certificates/deviceManager.ftl"/>
+</linkset>
+
+<script src="chrome://pippki/content/device_manager.js"/>
+
+<hbox flex="1" style="margin:5px">
+ <!-- List of devices -->
+ <tree id="device_tree" seltype="single"
+ onselect="enableButtons();" hidecolumnpicker="true"
+ flex="1" style="min-width: 15em">
+ <treecols>
+ <treecol id="deviceCol" flex="1" primary="true" data-l10n-id="devmgr-devlist"/>
+ </treecols>
+ <treechildren id="device_list"/>
+ </tree>
+ <!-- / List of devices -->
+ <!-- Device status -->
+ <tree id="info_tree" seltype="single" hidecolumnpicker="true"
+ style="-moz-box-flex: 3; min-width: 10em">
+ <treecols>
+ <treecol id="title1Col" style="-moz-box-flex: 5" primary="true" data-l10n-id="devmgr-header-details"/>
+ <treecol id="title2Col" style="-moz-box-flex: 7" data-l10n-id="devmgr-header-value"/>
+ </treecols>
+ <treechildren id="info_list"/>
+ </tree>
+ <!-- / Device status -->
+ <vbox> <!-- Buttons for manipulating devices -->
+ <button id="login_button"
+ data-l10n-id="devmgr-button-login"
+ oncommand="doLogin();" disabled="true"/>
+ <button id="logout_button"
+ data-l10n-id="devmgr-button-logout"
+ oncommand="doLogout();" disabled="true"/>
+ <button id="change_pw_button"
+ data-l10n-id="devmgr-button-changepw"
+ oncommand="changePassword();" disabled="true"/>
+ <button id="load_button"
+ data-l10n-id="devmgr-button-load"
+ oncommand="doLoad();"/>
+ <button id="unload_button"
+ data-l10n-id="devmgr-button-unload"
+ oncommand="doUnload();" disabled="true"/>
+ <button id="fipsbutton"
+ data-l10n-id="devmgr-button-enable-fips"
+ oncommand="toggleFIPS();"/>
+ </vbox> <!-- / Buttons for manipulating devices -->
+</hbox>
+
+</dialog>
+</window>
diff --git a/security/manager/pki/resources/content/downloadcert.js b/security/manager/pki/resources/content/downloadcert.js
new file mode 100644
index 0000000000..8451997441
--- /dev/null
+++ b/security/manager/pki/resources/content/downloadcert.js
@@ -0,0 +1,83 @@
+/* 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/. */
+/* import-globals-from pippki.js */
+"use strict";
+
+/**
+ * @file Implements the functionality of downloadcert.xhtml: a dialog that allows
+ * a user to confirm whether to import a certificate, and if so what trust
+ * to give it.
+ * @param {nsISupports} window.arguments.0
+ * Certificate to confirm import of, queryable to nsIX509Cert.
+ * @param {nsISupports} window.arguments.1
+ * Object to set the return values of calling the dialog on, queryable
+ * to the underlying type of DownloadCertReturnValues.
+ */
+
+/**
+ * @typedef DownloadCertReturnValues
+ * @type {nsIWritablePropertyBag2}
+ * @property {boolean} importConfirmed
+ * Set to true if the user confirmed import of the cert and accepted
+ * the dialog, false otherwise.
+ * @property {boolean} trustForSSL
+ * Set to true if the cert should be trusted for SSL, false otherwise.
+ * Undefined value if |importConfirmed| is not true.
+ * @property {boolean} trustForEmail
+ * Set to true if the cert should be trusted for e-mail, false
+ * otherwise. Undefined value if |importConfirmed| is not true.
+ */
+
+/**
+ * The cert to potentially import.
+ *
+ * @type {nsIX509Cert}
+ */
+var gCert;
+
+/**
+ * onload() handler.
+ */
+function onLoad() {
+ gCert = window.arguments[0].QueryInterface(Ci.nsIX509Cert);
+
+ document.addEventListener("dialogaccept", onDialogAccept);
+ document.addEventListener("dialogcancel", onDialogCancel);
+
+ let bundle = document.getElementById("pippki_bundle");
+ let caName = gCert.commonName;
+ if (!caName.length) {
+ caName = bundle.getString("unnamedCA");
+ }
+
+ setText("trustHeader", bundle.getFormattedString("newCAMessage1", [caName]));
+}
+
+/**
+ * Handler for the "View Cert" button.
+ */
+function viewCert() {
+ viewCertHelper(window, gCert, "window");
+}
+
+/**
+ * ondialogaccept() handler.
+ */
+function onDialogAccept() {
+ let checkSSL = document.getElementById("trustSSL");
+ let checkEmail = document.getElementById("trustEmail");
+
+ let retVals = window.arguments[1].QueryInterface(Ci.nsIWritablePropertyBag2);
+ retVals.setPropertyAsBool("importConfirmed", true);
+ retVals.setPropertyAsBool("trustForSSL", checkSSL.checked);
+ retVals.setPropertyAsBool("trustForEmail", checkEmail.checked);
+}
+
+/**
+ * ondialogcancel() handler.
+ */
+function onDialogCancel() {
+ let retVals = window.arguments[1].QueryInterface(Ci.nsIWritablePropertyBag2);
+ retVals.setPropertyAsBool("importConfirmed", false);
+}
diff --git a/security/manager/pki/resources/content/downloadcert.xhtml b/security/manager/pki/resources/content/downloadcert.xhtml
new file mode 100644
index 0000000000..bec39af4a7
--- /dev/null
+++ b/security/manager/pki/resources/content/downloadcert.xhtml
@@ -0,0 +1,61 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+<window data-l10n-id="download-cert-window2"
+ data-l10n-attrs="title, style"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="onLoad();">
+<dialog id="download_cert"
+ buttons="accept,cancel">
+
+<linkset>
+ <html:link rel="localization" href="security/pippki/pippki.ftl"/>
+</linkset>
+
+<stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/>
+
+<script src="chrome://pippki/content/pippki.js"/>
+<script src="chrome://pippki/content/downloadcert.js"/>
+
+ <!-- Let 'em know what they're doing -->
+ <vbox>
+ <description data-l10n-id="download-cert-message"></description>
+ </vbox>
+
+ <separator/>
+
+ <!-- checkboxes for trust bits
+ - "do you want to?"
+ - * trust for SSL
+ - * trust for email
+ -->
+ <vbox>
+ <description id="trustHeader"/>
+ <checkbox data-l10n-id="download-cert-trust-ssl"
+ id="trustSSL"/>
+ <checkbox data-l10n-id="download-cert-trust-email"
+ id="trustEmail"/>
+ </vbox>
+
+ <separator/>
+
+ <vbox>
+ <description data-l10n-id="download-cert-message-desc"></description>
+ <separator/>
+ <hbox>
+ <button id="viewC-button"
+ data-l10n-id="download-cert-view-cert"
+ oncommand="viewCert();"/>
+ <description style="margin: 4px;" data-l10n-id="download-cert-view-text"></description>
+ </hbox>
+ </vbox>
+
+</dialog>
+</window>
diff --git a/security/manager/pki/resources/content/editcacert.js b/security/manager/pki/resources/content/editcacert.js
new file mode 100644
index 0000000000..c482bd6e0a
--- /dev/null
+++ b/security/manager/pki/resources/content/editcacert.js
@@ -0,0 +1,55 @@
+/* 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/. */
+/* import-globals-from pippki.js */
+"use strict";
+
+var gCertDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+);
+/**
+ * Cert to edit the trust of.
+ *
+ * @type {nsIX509Cert}
+ */
+var gCert;
+
+/**
+ * onload() handler.
+ */
+function onLoad() {
+ gCert = window.arguments[0];
+
+ document.addEventListener("dialogaccept", onDialogAccept);
+
+ let certMsg = document.getElementById("certmsg");
+ document.l10n.setAttributes(certMsg, "edit-trust-ca", {
+ certName: gCert.commonName,
+ });
+
+ let sslCheckbox = document.getElementById("trustSSL");
+ sslCheckbox.checked = gCertDB.isCertTrusted(
+ gCert,
+ Ci.nsIX509Cert.CA_CERT,
+ Ci.nsIX509CertDB.TRUSTED_SSL
+ );
+
+ let emailCheckbox = document.getElementById("trustEmail");
+ emailCheckbox.checked = gCertDB.isCertTrusted(
+ gCert,
+ Ci.nsIX509Cert.CA_CERT,
+ Ci.nsIX509CertDB.TRUSTED_EMAIL
+ );
+}
+
+/**
+ * ondialogaccept() handler.
+ */
+function onDialogAccept() {
+ let sslCheckbox = document.getElementById("trustSSL");
+ let emailCheckbox = document.getElementById("trustEmail");
+ let trustSSL = sslCheckbox.checked ? Ci.nsIX509CertDB.TRUSTED_SSL : 0;
+ let trustEmail = emailCheckbox.checked ? Ci.nsIX509CertDB.TRUSTED_EMAIL : 0;
+
+ gCertDB.setCertTrust(gCert, Ci.nsIX509Cert.CA_CERT, trustSSL | trustEmail);
+}
diff --git a/security/manager/pki/resources/content/editcacert.xhtml b/security/manager/pki/resources/content/editcacert.xhtml
new file mode 100644
index 0000000000..745f05fdf3
--- /dev/null
+++ b/security/manager/pki/resources/content/editcacert.xhtml
@@ -0,0 +1,36 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+<window data-l10n-id="certmgr-edit-ca-cert2"
+ data-l10n-attrs="style"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="onLoad();">
+<dialog id="editCaCert"
+ buttons="accept,cancel">
+
+ <linkset>
+ <html:link rel="localization" href="security/certificates/certManager.ftl"/>
+ </linkset>
+
+ <script src="chrome://pippki/content/pippki.js"/>
+ <script src="chrome://pippki/content/editcacert.js"/>
+
+ <description id="certmsg"/>
+ <separator/>
+ <description data-l10n-id="certmgr-edit-cert-edit-trust"></description>
+ <vbox align="start">
+ <checkbox data-l10n-id="certmgr-edit-cert-trust-ssl"
+ id="trustSSL"/>
+ <checkbox data-l10n-id="certmgr-edit-cert-trust-email"
+ id="trustEmail"/>
+ </vbox>
+
+</dialog>
+</window>
diff --git a/security/manager/pki/resources/content/exceptionDialog.css b/security/manager/pki/resources/content/exceptionDialog.css
new file mode 100644
index 0000000000..0aaf917330
--- /dev/null
+++ b/security/manager/pki/resources/content/exceptionDialog.css
@@ -0,0 +1,38 @@
+/* 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/. */
+
+:root {
+ max-width: 40em;
+}
+
+#warningSupplemental {
+ font-weight: bold;
+}
+
+.description {
+ font-weight: bold;
+}
+
+.longDescription {
+ /* 40em is the max-width of the dialog defined above.
+ 18px is the horizontal padding of the dialog. */
+ width: calc(40em - 18px);
+ padding-bottom: 1em;
+}
+
+#warningText,
+#warningSupplemental,
+#headerDescription,
+.longDescription {
+ white-space: pre-wrap;
+}
+
+.description:empty,
+.longDescription:empty {
+ display: none;
+}
+
+#locationTextBox {
+ -moz-box-flex: 1;
+}
diff --git a/security/manager/pki/resources/content/exceptionDialog.js b/security/manager/pki/resources/content/exceptionDialog.js
new file mode 100644
index 0000000000..1953cce8fa
--- /dev/null
+++ b/security/manager/pki/resources/content/exceptionDialog.js
@@ -0,0 +1,334 @@
+/* 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/. */
+/* import-globals-from pippki.js */
+"use strict";
+
+var gDialog;
+var gSecInfo;
+var gCert;
+var gChecking;
+var gBroken;
+var gNeedReset;
+
+const { PrivateBrowsingUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/PrivateBrowsingUtils.sys.mjs"
+);
+
+function initExceptionDialog() {
+ gNeedReset = false;
+ gDialog = document.getElementById("exceptiondialog");
+ let warningText = document.getElementById("warningText");
+ document.l10n.setAttributes(warningText, "add-exception-branded-warning");
+ let confirmButton = gDialog.getButton("extra1");
+ let l10nUpdatedElements = [confirmButton, warningText];
+ confirmButton.disabled = true;
+
+ var args = window.arguments;
+ if (args && args[0]) {
+ if (args[0].location) {
+ // We were pre-seeded with a location.
+ document.getElementById("locationTextBox").value = args[0].location;
+ document.getElementById("checkCertButton").disabled = false;
+
+ if (args[0].securityInfo) {
+ gSecInfo = args[0].securityInfo;
+ gCert = gSecInfo.serverCert;
+ gBroken = true;
+ l10nUpdatedElements = l10nUpdatedElements.concat(updateCertStatus());
+ } else if (args[0].prefetchCert) {
+ // We can optionally pre-fetch the certificate too. Don't do this
+ // synchronously, since it would prevent the window from appearing
+ // until the fetch is completed, which could be multiple seconds.
+ // Instead, let's use a timer to spawn the actual fetch, but update
+ // the dialog to "checking..." state right away, so that the UI
+ // is appropriately responsive. Bug 453855
+ document.getElementById("checkCertButton").disabled = true;
+ gChecking = true;
+ l10nUpdatedElements = l10nUpdatedElements.concat(updateCertStatus());
+
+ window.setTimeout(checkCert, 0);
+ }
+ }
+
+ // Set out parameter to false by default
+ args[0].exceptionAdded = false;
+ }
+
+ for (let id of [
+ "warningSupplemental",
+ "certLocationLabel",
+ "checkCertButton",
+ "statusDescription",
+ "statusLongDescription",
+ "viewCertButton",
+ "permanent",
+ ]) {
+ let element = document.getElementById(id);
+ l10nUpdatedElements.push(element);
+ }
+
+ document.l10n
+ .translateElements(l10nUpdatedElements)
+ .then(() => window.sizeToContent());
+
+ document.addEventListener("dialogextra1", addException);
+ document.addEventListener("dialogextra2", checkCert);
+}
+
+/**
+ * Helper function for checkCert. Set as the onerror/onload callbacks for an
+ * XMLHttpRequest. Sets gSecInfo, gCert, gBroken, and gChecking according to
+ * the load information from the request. Probably should not be used directly.
+ *
+ * @param {XMLHttpRequest} req
+ * The XMLHttpRequest created and sent by checkCert.
+ * @param {Event} evt
+ * The load or error event.
+ */
+function grabCert(req, evt) {
+ if (req.channel && req.channel.securityInfo) {
+ gSecInfo = req.channel.securityInfo;
+ gCert = gSecInfo ? gSecInfo.serverCert : null;
+ }
+ gBroken = evt.type == "error";
+ gChecking = false;
+ document.l10n
+ .translateElements(updateCertStatus())
+ .then(() => window.sizeToContent());
+}
+
+/**
+ * Attempt to download the certificate for the location specified, and populate
+ * the Certificate Status section with the result.
+ */
+async function checkCert() {
+ gCert = null;
+ gSecInfo = null;
+ gChecking = true;
+ gBroken = false;
+ await document.l10n.translateElements(updateCertStatus());
+ window.sizeToContent();
+
+ let uri = getURI();
+
+ if (uri) {
+ let req = new XMLHttpRequest();
+ req.open("GET", uri.prePath);
+ req.onerror = grabCert.bind(this, req);
+ req.onload = grabCert.bind(this, req);
+ req.send(null);
+ } else {
+ gChecking = false;
+ await document.l10n.translateElements(updateCertStatus());
+ window.sizeToContent();
+ }
+}
+
+/**
+ * Build and return a URI, based on the information supplied in the
+ * Certificate Location fields
+ *
+ * @returns {nsIURI}
+ * URI constructed from the information supplied on success, null
+ * otherwise.
+ */
+function getURI() {
+ // Use fixup service instead of just ioservice's newURI since it's quite
+ // likely that the host will be supplied without a protocol prefix, resulting
+ // in malformed uri exceptions being thrown.
+ let locationTextBox = document.getElementById("locationTextBox");
+ let { preferredURI: uri } = Services.uriFixup.getFixupURIInfo(
+ locationTextBox.value
+ );
+
+ if (!uri) {
+ return null;
+ }
+
+ let mutator = uri.mutate();
+ if (uri.scheme == "http") {
+ mutator.setScheme("https");
+ }
+
+ if (uri.port == -1) {
+ mutator.setPort(443);
+ }
+
+ return mutator.finalize();
+}
+
+function resetDialog() {
+ document.getElementById("viewCertButton").disabled = true;
+ document.getElementById("permanent").disabled = true;
+ gDialog.getButton("extra1").disabled = true;
+ setText("headerDescription", "");
+ setText("statusDescription", "");
+ setText("statusLongDescription", "");
+ setText("status2Description", "");
+ setText("status2LongDescription", "");
+ setText("status3Description", "");
+ setText("status3LongDescription", "");
+ window.sizeToContent();
+}
+
+/**
+ * Called by input textboxes to manage UI state
+ */
+function handleTextChange() {
+ var checkCertButton = document.getElementById("checkCertButton");
+ checkCertButton.disabled = !document.getElementById("locationTextBox").value;
+ if (gNeedReset) {
+ gNeedReset = false;
+ resetDialog();
+ }
+}
+
+function updateCertStatus() {
+ var shortDesc, longDesc;
+ let l10nUpdatedElements = [];
+ if (gCert) {
+ if (gBroken) {
+ var mms = "add-exception-domain-mismatch-short";
+ var mml = "add-exception-domain-mismatch-long";
+ var exs = "add-exception-expired-short";
+ var exl = "add-exception-expired-long";
+ var uts = "add-exception-unverified-or-bad-signature-short";
+ var utl = "add-exception-unverified-or-bad-signature-long";
+ if (
+ gSecInfo.overridableErrorCategory ==
+ Ci.nsITransportSecurityInfo.ERROR_TRUST
+ ) {
+ shortDesc = uts;
+ longDesc = utl;
+ } else if (
+ gSecInfo.overridableErrorCategory ==
+ Ci.nsITransportSecurityInfo.ERROR_DOMAIN
+ ) {
+ shortDesc = mms;
+ longDesc = mml;
+ } else if (
+ gSecInfo.overridableErrorCategory ==
+ Ci.nsITransportSecurityInfo.ERROR_TIME
+ ) {
+ shortDesc = exs;
+ longDesc = exl;
+ }
+ // In these cases, we do want to enable the "Add Exception" button
+ gDialog.getButton("extra1").disabled = false;
+
+ // If the Private Browsing service is available and the mode is active,
+ // don't store permanent exceptions, since they would persist after
+ // private browsing mode was disabled.
+ var inPrivateBrowsing = inPrivateBrowsingMode();
+ var pe = document.getElementById("permanent");
+ pe.disabled = inPrivateBrowsing;
+ pe.checked = !inPrivateBrowsing;
+
+ let headerDescription = document.getElementById("headerDescription");
+ document.l10n.setAttributes(
+ headerDescription,
+ "add-exception-invalid-header"
+ );
+ l10nUpdatedElements.push(headerDescription);
+ } else {
+ shortDesc = "add-exception-valid-short";
+ longDesc = "add-exception-valid-long";
+ gDialog.getButton("extra1").disabled = true;
+ document.getElementById("permanent").disabled = true;
+ }
+
+ // We're done checking the certificate, so allow the user to check it again.
+ document.getElementById("checkCertButton").disabled = false;
+ document.getElementById("viewCertButton").disabled = false;
+
+ // Notify observers about the availability of the certificate
+ Services.obs.notifyObservers(null, "cert-exception-ui-ready");
+ } else if (gChecking) {
+ shortDesc = "add-exception-checking-short";
+ longDesc = "add-exception-checking-long";
+ // We're checking the certificate, so we disable the Get Certificate
+ // button to make sure that the user can't interrupt the process and
+ // trigger another certificate fetch.
+ document.getElementById("checkCertButton").disabled = true;
+ document.getElementById("viewCertButton").disabled = true;
+ gDialog.getButton("extra1").disabled = true;
+ document.getElementById("permanent").disabled = true;
+ } else {
+ shortDesc = "add-exception-no-cert-short";
+ longDesc = "add-exception-no-cert-long";
+ // We're done checking the certificate, so allow the user to check it again.
+ document.getElementById("checkCertButton").disabled = false;
+ document.getElementById("viewCertButton").disabled = true;
+ gDialog.getButton("extra1").disabled = true;
+ document.getElementById("permanent").disabled = true;
+ }
+ let statusDescription = document.getElementById("statusDescription");
+ let statusLongDescription = document.getElementById("statusLongDescription");
+ document.l10n.setAttributes(statusDescription, shortDesc);
+ document.l10n.setAttributes(statusLongDescription, longDesc);
+ l10nUpdatedElements.push(statusDescription);
+ l10nUpdatedElements.push(statusLongDescription);
+
+ gNeedReset = true;
+ return l10nUpdatedElements;
+}
+
+/**
+ * Handle user request to display certificate details
+ */
+function viewCertButtonClick() {
+ if (gCert) {
+ viewCertHelper(this, gCert);
+ }
+}
+
+/**
+ * Handle user request to add an exception for the specified cert
+ */
+function addException() {
+ if (!gCert || !gSecInfo) {
+ return;
+ }
+
+ var overrideService = Cc["@mozilla.org/security/certoverride;1"].getService(
+ Ci.nsICertOverrideService
+ );
+ var flags = 0;
+ if (gSecInfo.isUntrusted) {
+ flags |= overrideService.ERROR_UNTRUSTED;
+ }
+ if (gSecInfo.isDomainMismatch) {
+ flags |= overrideService.ERROR_MISMATCH;
+ }
+ if (gSecInfo.isNotValidAtThisTime) {
+ flags |= overrideService.ERROR_TIME;
+ }
+
+ var permanentCheckbox = document.getElementById("permanent");
+ var shouldStorePermanently =
+ permanentCheckbox.checked && !inPrivateBrowsingMode();
+ var uri = getURI();
+ overrideService.rememberValidityOverride(
+ uri.asciiHost,
+ uri.port,
+ {},
+ gCert,
+ flags,
+ !shouldStorePermanently
+ );
+
+ let args = window.arguments;
+ if (args && args[0]) {
+ args[0].exceptionAdded = true;
+ }
+
+ gDialog.acceptDialog();
+}
+
+/**
+ * @returns {boolean} Whether this dialog is in private browsing mode.
+ */
+function inPrivateBrowsingMode() {
+ return PrivateBrowsingUtils.isWindowPrivate(window);
+}
diff --git a/security/manager/pki/resources/content/exceptionDialog.xhtml b/security/manager/pki/resources/content/exceptionDialog.xhtml
new file mode 100644
index 0000000000..56be4bc5cb
--- /dev/null
+++ b/security/manager/pki/resources/content/exceptionDialog.xhtml
@@ -0,0 +1,88 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://pippki/content/exceptionDialog.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+<window windowtype="mozilla:exceptiondialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ data-l10n-id="exception-mgr"
+ onload="initExceptionDialog();">
+<dialog id="exceptiondialog"
+ buttonidextra1="exception-mgr-extra-button"
+ buttons="cancel,extra1,extra2"
+ defaultButton="extra2">
+
+ <linkset>
+ <html:link rel="localization" href="branding/brand.ftl"/>
+ <html:link rel="localization" href="security/certificates/certManager.ftl"/>
+ </linkset>
+
+ <script src="chrome://global/content/globalOverlay.js"/>
+ <script src="chrome://global/content/editMenuOverlay.js"/>
+
+ <script src="chrome://pippki/content/pippki.js"/>
+ <script src="chrome://pippki/content/exceptionDialog.js"/>
+
+ <hbox>
+ <vbox>
+#ifdef MOZ_WIDGET_GTK
+ <image src="moz-icon://stock/gtk-dialog-warning?size=dialog"/>
+#else
+ <image src="chrome://global/skin/icons/warning-large.png"/>
+#endif
+ <spacer flex="1"/>
+ </vbox>
+ <vbox flex="1">
+ <!-- Note that because of the styling, there must be no whitespace within
+ the description tags -->
+ <description id="warningText"/>
+ <description id="warningSupplemental"
+ data-l10n-id="exception-mgr-supplemental-warning"/>
+ </vbox>
+ </hbox>
+
+ <hbox align="center">
+ <label control="locationTextBox"
+ id="certLocationLabel"
+ data-l10n-id="exception-mgr-cert-location-url"/>
+ <html:input id="locationTextBox"
+ oninput="handleTextChange();"
+ value="https://"
+ class="uri-element"/>
+ <button id="checkCertButton"
+ disabled="true"
+ dlgtype="extra2"
+ data-l10n-id="exception-mgr-cert-location-download"/>
+ </hbox>
+
+ <hbox align="center">
+ <description id="headerDescription"
+ flex="1"/>
+ <button id="viewCertButton"
+ data-l10n-id="exception-mgr-cert-status-view-cert"
+ disabled="true"
+ oncommand="viewCertButtonClick();"/>
+ </hbox>
+ <description id="statusDescription"
+ class="description"/>
+ <description id="statusLongDescription"
+ class="longDescription"/>
+ <description id="status2Description"
+ class="description"/>
+ <description id="status2LongDescription"
+ class="longDescription"/>
+ <description id="status3Description"
+ class="description"/>
+ <description id="status3LongDescription"
+ class="longDescription"/>
+ <checkbox id="permanent"
+ disabled="true"
+ data-l10n-id="exception-mgr-permanent"/>
+</dialog>
+</window>
diff --git a/security/manager/pki/resources/content/load_device.js b/security/manager/pki/resources/content/load_device.js
new file mode 100644
index 0000000000..0f77e1f1bd
--- /dev/null
+++ b/security/manager/pki/resources/content/load_device.js
@@ -0,0 +1,75 @@
+/* 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/. */
+/* import-globals-from pippki.js */
+"use strict";
+
+document.addEventListener("dialogaccept", onDialogAccept);
+
+/**
+ * @file Implements the functionality of load_device.xhtml: a dialog that allows
+ * a PKCS #11 module to be loaded into Firefox.
+ */
+
+async function onBrowseBtnPress() {
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let [loadPK11ModuleFilePickerTitle] = await document.l10n.formatValues([
+ { id: "load-pk11-module-file-picker-title" },
+ ]);
+ fp.init(window, loadPK11ModuleFilePickerTitle, Ci.nsIFilePicker.modeOpen);
+ fp.appendFilters(Ci.nsIFilePicker.filterAll);
+ fp.open(rv => {
+ if (rv == Ci.nsIFilePicker.returnOK) {
+ document.getElementById("device_path").value = fp.file.path;
+ }
+
+ // This notification gets sent solely for test purposes. It should not be
+ // used by production code.
+ Services.obs.notifyObservers(window, "LoadPKCS11Module:FilePickHandled");
+ });
+}
+
+/**
+ * ondialogaccept() handler.
+ *
+ * @param {object} event
+ * The event causing this handler function to be called.
+ */
+function onDialogAccept(event) {
+ let nameBox = document.getElementById("device_name");
+ let pathBox = document.getElementById("device_path");
+ let pkcs11ModuleDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].getService(
+ Ci.nsIPKCS11ModuleDB
+ );
+
+ try {
+ pkcs11ModuleDB.addModule(nameBox.value, pathBox.value, 0, 0);
+ } catch (e) {
+ addModuleFailure("add-module-failure");
+ event.preventDefault();
+ }
+}
+
+async function addModuleFailure(l10nID) {
+ let [AddModuleFailure] = await document.l10n.formatValues([{ id: l10nID }]);
+ alertPromptService(null, AddModuleFailure);
+}
+
+function validateModuleName() {
+ let name = document.getElementById("device_name").value;
+ let helpText = document.getElementById("helpText");
+ helpText.value = "";
+ let dialogNode = document.querySelector("dialog");
+ dialogNode.removeAttribute("buttondisabledaccept");
+ if (name == "") {
+ document.l10n.setAttributes(helpText, "load-module-help-empty-module-name");
+ dialogNode.setAttribute("buttondisabledaccept", true);
+ }
+ if (name == "Root Certs") {
+ document.l10n.setAttributes(
+ helpText,
+ "load-module-help-root-certs-module-name"
+ );
+ dialogNode.setAttribute("buttondisabledaccept", true);
+ }
+}
diff --git a/security/manager/pki/resources/content/load_device.xhtml b/security/manager/pki/resources/content/load_device.xhtml
new file mode 100644
index 0000000000..3f43b9e65e
--- /dev/null
+++ b/security/manager/pki/resources/content/load_device.xhtml
@@ -0,0 +1,53 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ data-l10n-id="load-device">
+<dialog id="loaddevice"
+ buttons="accept,cancel">
+
+ <linkset>
+ <html:link rel="localization" href="security/certificates/deviceManager.ftl"/>
+ </linkset>
+
+ <script src="chrome://global/content/globalOverlay.js"/>
+ <script src="chrome://global/content/editMenuOverlay.js"/>
+
+ <script src="chrome://pippki/content/pippki.js"/>
+ <script src="chrome://pippki/content/load_device.js"/>
+
+ <html:style>
+ #device_name,
+ #device_path {
+ -moz-box-flex: 1;
+ }
+ </html:style>
+
+ <description data-l10n-id="load-device-info"></description>
+ <hbox align="center">
+ <label data-l10n-id="load-device-modname"
+ control="device_name"/>
+ <html:input id="device_name"
+ data-l10n-id="load-device-modname-default"
+ data-l10n-attrs="value"
+ onchange="validateModuleName();"/>
+ </hbox>
+ <hbox align="center">
+ <label data-l10n-id="load-device-filename"
+ control="device_path"/>
+ <html:input id="device_path" />
+ <button id="browse" flex="1"
+ data-l10n-id="load-device-browse"
+ oncommand="onBrowseBtnPress();"/>
+ </hbox>
+ <label id="helpText" value=""/>
+
+</dialog>
+</window>
diff --git a/security/manager/pki/resources/content/pippki.js b/security/manager/pki/resources/content/pippki.js
new file mode 100644
index 0000000000..7c2f8f3255
--- /dev/null
+++ b/security/manager/pki/resources/content/pippki.js
@@ -0,0 +1,301 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+"use strict";
+
+/*
+ * These are helper functions to be included
+ * pippki UI js files.
+ */
+
+function setText(id, value) {
+ let element = document.getElementById(id);
+ if (!element) {
+ return;
+ }
+ if (element.hasChildNodes()) {
+ element.firstChild.remove();
+ }
+ element.appendChild(document.createTextNode(value));
+}
+
+async function viewCertHelper(parent, cert, openingOption = "tab") {
+ if (!cert) {
+ return;
+ }
+
+ let win = Services.wm.getMostRecentBrowserWindow();
+ let results = await asyncDetermineUsages(cert);
+ let chain = getBestChain(results);
+ if (!chain) {
+ chain = [cert];
+ }
+ let certs = chain.map(elem => encodeURIComponent(elem.getBase64DERString()));
+ let certsStringURL = certs.map(elem => `cert=${elem}`);
+ certsStringURL = certsStringURL.join("&");
+ let url = `about:certificate?${certsStringURL}`;
+ let opened = win.switchToTabHavingURI(url, false, {});
+ if (!opened) {
+ win.openTrustedLinkIn(url, openingOption);
+ }
+}
+
+function getPKCS7Array(certArray) {
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ let pkcs7String = certdb.asPKCS7Blob(certArray);
+ let pkcs7Array = new Uint8Array(pkcs7String.length);
+ for (let i = 0; i < pkcs7Array.length; i++) {
+ pkcs7Array[i] = pkcs7String.charCodeAt(i);
+ }
+ return pkcs7Array;
+}
+
+function getPEMString(cert) {
+ var derb64 = cert.getBase64DERString();
+ // Wrap the Base64 string into lines of 64 characters with CRLF line breaks
+ // (as specified in RFC 1421).
+ var wrapped = derb64.replace(/(\S{64}(?!$))/g, "$1\r\n");
+ return (
+ "-----BEGIN CERTIFICATE-----\r\n" +
+ wrapped +
+ "\r\n-----END CERTIFICATE-----\r\n"
+ );
+}
+
+function alertPromptService(title, message) {
+ // XXX Bug 1425832 - Using Services.prompt here causes tests to report memory
+ // leaks.
+ // eslint-disable-next-line mozilla/use-services
+ var ps = Cc["@mozilla.org/prompter;1"].getService(Ci.nsIPromptService);
+ ps.alert(window, title, message);
+}
+
+const DEFAULT_CERT_EXTENSION = "crt";
+
+/**
+ * Generates a filename for a cert suitable to set as the |defaultString|
+ * attribute on an Ci.nsIFilePicker.
+ *
+ * @param {nsIX509Cert} cert
+ * The cert to generate a filename for.
+ * @returns {string}
+ * Generated filename.
+ */
+function certToFilename(cert) {
+ let filename = cert.displayName;
+
+ // Remove unneeded and/or unsafe characters.
+ filename = filename
+ .replace(/\s/g, "")
+ .replace(/\./g, "_")
+ .replace(/\\/g, "")
+ .replace(/\//g, "");
+
+ // Ci.nsIFilePicker.defaultExtension is more of a suggestion to some
+ // implementations, so we include the extension in the file name as well. This
+ // is what the documentation for Ci.nsIFilePicker.defaultString says we should do
+ // anyways.
+ return `${filename}.${DEFAULT_CERT_EXTENSION}`;
+}
+
+async function exportToFile(parent, cert) {
+ if (!cert) {
+ return;
+ }
+
+ let results = await asyncDetermineUsages(cert);
+ let chain = getBestChain(results);
+ if (!chain) {
+ chain = [cert];
+ }
+
+ let formats = {
+ base64: "*.crt; *.pem",
+ "base64-chain": "*.crt; *.pem",
+ der: "*.der",
+ pkcs7: "*.p7c",
+ "pkcs7-chain": "*.p7c",
+ };
+ let [saveCertAs, ...formatLabels] = await document.l10n.formatValues(
+ [
+ "save-cert-as",
+ ...Object.keys(formats).map(f => "cert-format-" + f),
+ ].map(id => ({ id }))
+ );
+
+ var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ fp.init(parent, saveCertAs, Ci.nsIFilePicker.modeSave);
+ fp.defaultString = certToFilename(cert);
+ fp.defaultExtension = DEFAULT_CERT_EXTENSION;
+ for (let format of Object.values(formats)) {
+ fp.appendFilter(formatLabels.shift(), format);
+ }
+ fp.appendFilters(Ci.nsIFilePicker.filterAll);
+ let filePickerResult = await new Promise(resolve => {
+ fp.open(resolve);
+ });
+
+ if (
+ filePickerResult != Ci.nsIFilePicker.returnOK &&
+ filePickerResult != Ci.nsIFilePicker.returnReplace
+ ) {
+ return;
+ }
+
+ var content = "";
+ switch (fp.filterIndex) {
+ case 1:
+ content = getPEMString(cert);
+ for (let i = 1; i < chain.length; i++) {
+ content += getPEMString(chain[i]);
+ }
+ break;
+ case 2:
+ // IOUtils.write requires a typed array.
+ // nsIX509Cert.getRawDER() returns an array (not a typed array), so we
+ // convert it here.
+ content = Uint8Array.from(cert.getRawDER());
+ break;
+ case 3:
+ // getPKCS7Array returns a typed array already, so no conversion is
+ // necessary.
+ content = getPKCS7Array([cert]);
+ break;
+ case 4:
+ content = getPKCS7Array(chain);
+ break;
+ case 0:
+ default:
+ content = getPEMString(cert);
+ break;
+ }
+
+ if (typeof content === "string") {
+ content = new TextEncoder().encode(content);
+ }
+
+ try {
+ await IOUtils.write(fp.file.path, content);
+ } catch (ex) {
+ let title = await document.l10n.formatValue("write-file-failure");
+ alertPromptService(title, ex.toString());
+ }
+ if (Cu.isInAutomation) {
+ Services.obs.notifyObservers(null, "cert-export-finished");
+ }
+}
+
+const PRErrorCodeSuccess = 0;
+
+// Certificate usages we care about in the certificate viewer.
+const certificateUsageSSLClient = 0x0001;
+const certificateUsageSSLServer = 0x0002;
+const certificateUsageSSLCA = 0x0008;
+const certificateUsageEmailSigner = 0x0010;
+const certificateUsageEmailRecipient = 0x0020;
+
+// A map from the name of a certificate usage to the value of the usage.
+// Useful for printing debugging information and for enumerating all supported
+// usages.
+const certificateUsages = {
+ certificateUsageSSLClient,
+ certificateUsageSSLServer,
+ certificateUsageSSLCA,
+ certificateUsageEmailSigner,
+ certificateUsageEmailRecipient,
+};
+
+/**
+ * Returns a promise that will resolve with a results array consisting of what
+ * usages the given certificate successfully verified for.
+ *
+ * @param {nsIX509Cert} cert
+ * The certificate to determine valid usages for.
+ * @returns {Promise}
+ * A promise that will resolve with the results of the verifications.
+ */
+function asyncDetermineUsages(cert) {
+ let promises = [];
+ let now = Date.now() / 1000;
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ Object.keys(certificateUsages).forEach(usageString => {
+ promises.push(
+ new Promise((resolve, reject) => {
+ let usage = certificateUsages[usageString];
+ certdb.asyncVerifyCertAtTime(
+ cert,
+ usage,
+ 0,
+ null,
+ now,
+ (aPRErrorCode, aVerifiedChain, aHasEVPolicy) => {
+ resolve({
+ usageString,
+ errorCode: aPRErrorCode,
+ chain: aVerifiedChain,
+ });
+ }
+ );
+ })
+ );
+ });
+ return Promise.all(promises);
+}
+
+/**
+ * Given a results array, returns the "best" verified certificate chain. Since
+ * the primary use case is for TLS server certificates in Firefox, such a
+ * verified chain will be returned if present. Otherwise, the priority is: TLS
+ * client certificate, email signer, email recipient, CA. Returns null if no
+ * usage verified successfully.
+ *
+ * @param {Array} results
+ * An array of results from `asyncDetermineUsages`. See `displayUsages`.
+ * @returns {Array} An array of `nsIX509Cert` representing the verified
+ * certificate chain for the given usage, or null if there is none.
+ */
+function getBestChain(results) {
+ let usages = [
+ certificateUsageSSLServer,
+ certificateUsageSSLClient,
+ certificateUsageEmailSigner,
+ certificateUsageEmailRecipient,
+ certificateUsageSSLCA,
+ ];
+ for (let usage of usages) {
+ let chain = getChainForUsage(results, usage);
+ if (chain) {
+ return chain;
+ }
+ }
+ return null;
+}
+
+/**
+ * Given a results array, returns the chain corresponding to the desired usage,
+ * if verifying for that usage succeeded. Returns null otherwise.
+ *
+ * @param {Array} results
+ * An array of results from `asyncDetermineUsages`. See `displayUsages`.
+ * @param {number} usage
+ * A numerical value corresponding to a usage. See `certificateUsages`.
+ * @returns {Array} An array of `nsIX509Cert` representing the verified
+ * certificate chain for the given usage, or null if there is none.
+ */
+function getChainForUsage(results, usage) {
+ for (let result of results) {
+ if (
+ certificateUsages[result.usageString] == usage &&
+ result.errorCode == PRErrorCodeSuccess
+ ) {
+ return result.chain;
+ }
+ }
+ return null;
+}
diff --git a/security/manager/pki/resources/content/resetpassword.js b/security/manager/pki/resources/content/resetpassword.js
new file mode 100644
index 0000000000..30db349794
--- /dev/null
+++ b/security/manager/pki/resources/content/resetpassword.js
@@ -0,0 +1,28 @@
+/* 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/. */
+/* import-globals-from pippki.js */
+"use strict";
+
+document.addEventListener("dialogaccept", resetPassword);
+
+function resetPassword() {
+ var pk11db = Cc["@mozilla.org/security/pk11tokendb;1"].getService(
+ Ci.nsIPK11TokenDB
+ );
+ var token = pk11db.getInternalKeyToken();
+ token.reset();
+
+ try {
+ Services.logins.removeAllUserFacingLogins();
+ } catch (e) {}
+
+ let l10n = new Localization(["security/pippki/pippki.ftl"], true);
+ if (l10n) {
+ Services.prompt.alert(
+ window,
+ l10n.formatValueSync("pippki-reset-password-confirmation-title"),
+ l10n.formatValueSync("pippki-reset-password-confirmation-message")
+ );
+ }
+}
diff --git a/security/manager/pki/resources/content/resetpassword.xhtml b/security/manager/pki/resources/content/resetpassword.xhtml
new file mode 100644
index 0000000000..397ee1ab27
--- /dev/null
+++ b/security/manager/pki/resources/content/resetpassword.xhtml
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+<window data-l10n-id="reset-primary-password-window2"
+ data-l10n-attrs="title, style"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+<dialog id="reset_password"
+ buttons="accept,cancel"
+ buttonidaccept="reset-password-button-label"
+ defaultButton="cancel">
+
+<linkset>
+ <html:link rel="localization" href="security/pippki/pippki.ftl"/>
+</linkset>
+
+ <stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/>
+
+ <script src="chrome://pippki/content/pippki.js"/>
+ <script src="chrome://pippki/content/resetpassword.js"/>
+
+ <hbox flex="1">
+ <vbox>
+ <image class="alert-icon" style="margin: 5px;"/>
+ </vbox>
+ <vbox style="margin: 5px;" flex="1">
+ <hbox flex="1">
+ <vbox flex="1">
+ <description data-l10n-id="reset-primary-password-text"></description>
+ </vbox>
+ </hbox>
+ </vbox>
+ </hbox>
+</dialog>
+</window>
diff --git a/security/manager/pki/resources/content/setp12password.js b/security/manager/pki/resources/content/setp12password.js
new file mode 100644
index 0000000000..14200c36ce
--- /dev/null
+++ b/security/manager/pki/resources/content/setp12password.js
@@ -0,0 +1,127 @@
+/* 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/. */
+"use strict";
+
+/**
+ * @file Implements the functionality of setp12password.xhtml: a dialog that lets
+ * the user confirm the password to set on a PKCS #12 file.
+ * @param {nsISupports} window.arguments.0
+ * Object to set the return values of calling the dialog on, queryable
+ * to the underlying type of SetP12PasswordReturnValues.
+ */
+
+/**
+ * @typedef SetP12PasswordReturnValues
+ * @type {nsIWritablePropertyBag2}
+ * @property {boolean} confirmedPassword
+ * Set to true if the user entered two matching passwords and
+ * confirmed the dialog.
+ * @property {string} password
+ * The password the user entered. Undefined value if
+ * |confirmedPassword| is not true.
+ */
+
+/**
+ * onload() handler.
+ */
+function onLoad() {
+ // Ensure the first password textbox has focus.
+ document.getElementById("pw1").focus();
+ document.addEventListener("dialogaccept", onDialogAccept);
+ document.addEventListener("dialogcancel", onDialogCancel);
+}
+
+/**
+ * ondialogaccept() handler.
+ */
+function onDialogAccept() {
+ let password = document.getElementById("pw1").value;
+
+ let retVals = window.arguments[0].QueryInterface(Ci.nsIWritablePropertyBag2);
+ retVals.setPropertyAsBool("confirmedPassword", true);
+ retVals.setPropertyAsAString("password", password);
+}
+
+/**
+ * ondialogcancel() handler.
+ */
+function onDialogCancel() {
+ let retVals = window.arguments[0].QueryInterface(Ci.nsIWritablePropertyBag2);
+ retVals.setPropertyAsBool("confirmedPassword", false);
+}
+
+/**
+ * Calculates the strength of the given password, suitable for use in updating
+ * a progress bar that represents said strength.
+ *
+ * The strength of the password is calculated by checking the number of:
+ * - Characters
+ * - Numbers
+ * - Non-alphanumeric chars
+ * - Upper case characters
+ *
+ * @param {string} password
+ * The password to calculate the strength of.
+ * @returns {number}
+ * The strength of the password in the range [0, 100].
+ */
+function getPasswordStrength(password) {
+ let lengthStrength = password.length;
+ if (lengthStrength > 5) {
+ lengthStrength = 5;
+ }
+
+ let nonNumericChars = password.replace(/[0-9]/g, "");
+ let numericStrength = password.length - nonNumericChars.length;
+ if (numericStrength > 3) {
+ numericStrength = 3;
+ }
+
+ let nonSymbolChars = password.replace(/\W/g, "");
+ let symbolStrength = password.length - nonSymbolChars.length;
+ if (symbolStrength > 3) {
+ symbolStrength = 3;
+ }
+
+ let nonUpperAlphaChars = password.replace(/[A-Z]/g, "");
+ let upperAlphaStrength = password.length - nonUpperAlphaChars.length;
+ if (upperAlphaStrength > 3) {
+ upperAlphaStrength = 3;
+ }
+
+ let strength =
+ lengthStrength * 10 -
+ 20 +
+ numericStrength * 10 +
+ symbolStrength * 15 +
+ upperAlphaStrength * 10;
+ if (strength < 0) {
+ strength = 0;
+ }
+ if (strength > 100) {
+ strength = 100;
+ }
+
+ return strength;
+}
+
+/**
+ * oninput() handler for both password textboxes.
+ *
+ * @param {boolean} recalculatePasswordStrength
+ * Whether to recalculate the strength of the first password.
+ */
+function onPasswordInput(recalculatePasswordStrength) {
+ let pw1 = document.getElementById("pw1").value;
+
+ if (recalculatePasswordStrength) {
+ document.getElementById("pwmeter").value = getPasswordStrength(pw1);
+ }
+
+ // Disable the accept button if the two passwords don't match, and enable it
+ // if the passwords do match.
+ let pw2 = document.getElementById("pw2").value;
+ document.getElementById("setp12password").getButton("accept").disabled =
+ pw1 != pw2;
+}
diff --git a/security/manager/pki/resources/content/setp12password.xhtml b/security/manager/pki/resources/content/setp12password.xhtml
new file mode 100644
index 0000000000..b521f028ef
--- /dev/null
+++ b/security/manager/pki/resources/content/setp12password.xhtml
@@ -0,0 +1,48 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+<window data-l10n-id="set-password-window"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ style="width: 48em;"
+ onload="onLoad();">
+<dialog id="setp12password"
+ buttons="accept,cancel">
+
+<linkset>
+ <html:link rel="localization" href="security/pippki/pippki.ftl"/>
+</linkset>
+
+ <script src="chrome://global/content/globalOverlay.js"/>
+ <script src="chrome://global/content/editMenuOverlay.js"/>
+
+ <script src="chrome://pippki/content/setp12password.js"/>
+
+ <description data-l10n-id="set-password-message"></description>
+ <separator />
+ <vbox>
+ <hbox class="input-row">
+ <label flex="1" data-l10n-id="set-password-backup-pw"/>
+ <html:input id="pw1" type="password" oninput="onPasswordInput(true);"/>
+ </hbox>
+ <hbox class="input-row">
+ <label flex="1" data-l10n-id="set-password-repeat-backup-pw"/>
+ <html:input id="pw2" type="password" oninput="onPasswordInput(false);"/>
+ </hbox>
+ </vbox>
+ <separator/>
+ <description data-l10n-id="set-password-reminder"></description>
+ <separator/>
+
+ <vbox style="margin: 6px;">
+ <html:label for="pwmeter" style="display: -moz-box;" data-l10n-id="password-quality-meter"></html:label>
+ <html:progress id="pwmeter" value="0" max="100"/>
+ </vbox>
+</dialog>
+</window>