Source code
Revision control
Copy as Markdown
Other Tools
/* 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
/* import-globals-from pippki.js */
"use strict";
const gCertFileTypes = "*.p7b; *.crt; *.cert; *.cer; *.pem; *.der";
var { NetUtil } = ChromeUtils.importESModule(
"resource://gre/modules/NetUtil.sys.mjs"
);
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", "end");
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 => {
return {
hostPort: item.hostPort,
asciiHost: item.asciiHost,
port: item.port,
originAttributes: item.originAttributes,
fingerprint: item.fingerprint,
};
});
overrides.sort((a, b) => {
let criteria = ["hostPort", "fingerprint"];
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("host", item.asciiHost);
richlistitem.setAttribute("port", item.port);
richlistitem.setAttribute("hostPort", item.hostPort);
richlistitem.setAttribute("fingerprint", item.fingerprint);
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({ raw: item.fingerprint }));
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();
}
},
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 websiteDeleteButton = document.getElementById("websites_deleteButton");
websiteDeleteButton.disabled = this.richlist.selectedIndex < 0;
},
};
/**
* 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.browsingContext, 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.browsingContext, 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.browsingContext, 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.browsingContext, 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();
}
});
}