diff options
Diffstat (limited to 'security/manager/pki/resources/content/certManager.js')
-rw-r--r-- | security/manager/pki/resources/content/certManager.js | 824 |
1 files changed, 824 insertions, 0 deletions
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(); + } + }); +} |