1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 /* import-globals-from pippki.js */
7 const gCertFileTypes = "*.p7b; *.crt; *.cert; *.cer; *.pem; *.der";
9 var { NetUtil } = ChromeUtils.importESModule(
10 "resource://gre/modules/NetUtil.sys.mjs"
15 var certdialogs = Cc["@mozilla.org/nsCertificateDialogs;1"].getService(
16 Ci.nsICertificateDialogs
20 * List of certs currently selected in the active tab.
22 * @type {nsIX509Cert[]}
24 var selected_certs = [];
25 var selected_tree_items = [];
26 var selected_index = [];
30 * Cert tree for the "Authorities" tab.
36 * Cert tree for the "Servers" tab.
44 function createRichlistItem(item) {
45 let innerHbox = document.createXULElement("hbox");
46 innerHbox.setAttribute("align", "center");
47 innerHbox.setAttribute("flex", "1");
49 let row = document.createXULElement("label");
50 row.setAttribute("flex", "1");
51 row.setAttribute("crop", "end");
52 row.setAttribute("style", "margin-inline-start: 15px;");
54 row.setAttribute("value", item.raw);
56 document.l10n.setAttributes(row, item.l10nid);
58 row.setAttribute("ordinal", "1");
59 innerHbox.appendChild(row);
64 var serverRichList = {
68 let overrides = overrideService.getOverrides().map(item => {
70 hostPort: item.hostPort,
71 asciiHost: item.asciiHost,
73 originAttributes: item.originAttributes,
74 fingerprint: item.fingerprint,
77 overrides.sort((a, b) => {
78 let criteria = ["hostPort", "fingerprint"];
79 for (let c of criteria) {
80 let res = a[c].localeCompare(b[c]);
88 this.richlist.textContent = "";
89 this.richlist.clearSelection();
91 let frag = document.createDocumentFragment();
92 for (let override of overrides) {
93 let richlistitem = this._richBoxAddItem(override);
94 frag.appendChild(richlistitem);
96 this.richlist.appendChild(frag);
98 this._setButtonState();
99 this.richlist.addEventListener("select", () => this._setButtonState());
102 _richBoxAddItem(item) {
103 let richlistitem = document.createXULElement("richlistitem");
105 richlistitem.setAttribute("host", item.asciiHost);
106 richlistitem.setAttribute("port", item.port);
107 richlistitem.setAttribute("hostPort", item.hostPort);
108 richlistitem.setAttribute("fingerprint", item.fingerprint);
109 richlistitem.setAttribute(
111 JSON.stringify(item.originAttributes)
114 let hbox = document.createXULElement("hbox");
115 hbox.setAttribute("flex", "1");
116 hbox.setAttribute("equalsize", "always");
118 hbox.appendChild(createRichlistItem({ raw: item.hostPort }));
119 hbox.appendChild(createRichlistItem({ raw: item.fingerprint }));
121 richlistitem.appendChild(hbox);
126 deleteSelectedRichListItem() {
127 let selectedItem = this.richlist.selectedItem;
133 deleteConfirmed: false,
135 window.browsingContext.topChromeWindow.openDialog(
136 "chrome://pippki/content/deletecert.xhtml",
138 "chrome,centerscreen,modal",
142 hostPort: selectedItem.attributes.hostPort.value,
148 if (retVals.deleteConfirmed) {
149 overrideService.clearValidityOverride(
150 selectedItem.attributes.host.value,
151 selectedItem.attributes.port.value,
152 JSON.parse(selectedItem.attributes.originAttributes.value)
154 this.buildRichList();
160 exceptionAdded: false,
162 window.browsingContext.topChromeWindow.openDialog(
163 "chrome://pippki/content/exceptionDialog.xhtml",
165 "chrome,centerscreen,modal",
168 if (retval.exceptionAdded) {
169 this.buildRichList();
174 let websiteDeleteButton = document.getElementById("websites_deleteButton");
175 websiteDeleteButton.disabled = this.richlist.selectedIndex < 0;
179 * Cert tree for the "People" tab.
181 * @type {nsICertTree}
185 * Cert tree for the "Your Certificates" tab.
187 * @type {nsICertTree}
191 var clientAuthRememberService;
193 var rememberedDecisionsRichList = {
197 let rememberedDecisions = clientAuthRememberService.getDecisions();
199 let oldItems = this.richlist.querySelectorAll("richlistitem");
200 for (let item of oldItems) {
204 let frag = document.createDocumentFragment();
205 for (let decision of rememberedDecisions) {
206 let richlistitem = this._richBoxAddItem(decision);
207 frag.appendChild(richlistitem);
209 this.richlist.appendChild(frag);
211 this.richlist.addEventListener("select", () => this.setButtonState());
214 _richBoxAddItem(item) {
215 let richlistitem = document.createXULElement("richlistitem");
217 richlistitem.setAttribute("entryKey", item.entryKey);
218 richlistitem.setAttribute("dbKey", item.dbKey);
220 let hbox = document.createXULElement("hbox");
221 hbox.setAttribute("flex", "1");
222 hbox.setAttribute("equalsize", "always");
224 hbox.appendChild(createRichlistItem({ raw: item.asciiHost }));
225 if (item.dbKey == "") {
227 createRichlistItem({ l10nid: "send-no-client-certificate" })
230 hbox.appendChild(createRichlistItem({ raw: "" }));
232 let tmpCert = certdb.findCertByDBKey(item.dbKey);
233 // The certificate corresponding to this item's dbKey may not be
234 // available (for example, if it was stored on a token that's been
235 // removed, or if it was deleted).
237 hbox.appendChild(createRichlistItem({ raw: tmpCert.commonName }));
238 hbox.appendChild(createRichlistItem({ raw: tmpCert.serialNumber }));
241 createRichlistItem({ l10nid: "certificate-not-available" })
244 createRichlistItem({ l10nid: "certificate-not-available" })
249 richlistitem.appendChild(hbox);
254 deleteSelectedRichListItem() {
255 let selectedItem = this.richlist.selectedItem;
256 let index = this.richlist.selectedIndex;
261 clientAuthRememberService.forgetRememberedDecision(
262 selectedItem.attributes.entryKey.value
265 this.buildRichList();
266 this.setButtonState();
269 viewSelectedRichListItem() {
270 let selectedItem = this.richlist.selectedItem;
271 let index = this.richlist.selectedIndex;
276 if (selectedItem.attributes.dbKey.value != "") {
277 let cert = certdb.findCertByDBKey(selectedItem.attributes.dbKey.value);
278 viewCertHelper(window, cert);
283 let rememberedDeleteButton = document.getElementById(
284 "remembered_deleteButton"
286 let rememberedViewButton = document.getElementById("remembered_viewButton");
288 rememberedDeleteButton.disabled = this.richlist.selectedIndex < 0;
289 rememberedViewButton.disabled =
290 this.richlist.selectedItem == null
292 : this.richlist.selectedItem.attributes.dbKey.value == "";
296 function LoadCerts() {
297 certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
300 var certcache = certdb.getCerts();
302 caTreeView = Cc["@mozilla.org/security/nsCertTree;1"].createInstance(
305 caTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.CA_CERT);
306 document.getElementById("ca-tree").view = caTreeView;
308 emailTreeView = Cc["@mozilla.org/security/nsCertTree;1"].createInstance(
311 emailTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.EMAIL_CERT);
312 document.getElementById("email-tree").view = emailTreeView;
314 userTreeView = Cc["@mozilla.org/security/nsCertTree;1"].createInstance(
317 userTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.USER_CERT);
318 document.getElementById("user-tree").view = userTreeView;
320 clientAuthRememberService = Cc[
321 "@mozilla.org/security/clientAuthRememberService;1"
322 ].getService(Ci.nsIClientAuthRememberService);
324 overrideService = Cc["@mozilla.org/security/certoverride;1"].getService(
325 Ci.nsICertOverrideService
328 rememberedDecisionsRichList.richlist =
329 document.getElementById("rememberedList");
330 serverRichList.richlist = document.getElementById("serverList");
332 rememberedDecisionsRichList.buildRichList();
333 serverRichList.buildRichList();
335 rememberedDecisionsRichList.setButtonState();
337 enableBackupAllButton();
340 function enableBackupAllButton() {
341 let backupAllButton = document.getElementById("mine_backupAllButton");
342 backupAllButton.disabled = userTreeView.rowCount < 1;
345 function getSelectedCerts() {
346 var ca_tab = document.getElementById("ca_tab");
347 var mine_tab = document.getElementById("mine_tab");
348 var others_tab = document.getElementById("others_tab");
350 if (ca_tab.selected) {
351 items = caTreeView.selection;
352 } else if (mine_tab.selected) {
353 items = userTreeView.selection;
354 } else if (others_tab.selected) {
355 items = emailTreeView.selection;
361 nr = items.getRangeCount();
364 for (let i = 0; i < nr; i++) {
367 items.getRangeAt(i, o1, o2);
370 for (let j = min; j <= max; j++) {
371 if (ca_tab.selected) {
372 cert = caTreeView.getCert(j);
373 } else if (mine_tab.selected) {
374 cert = userTreeView.getCert(j);
375 } else if (others_tab.selected) {
376 cert = emailTreeView.getCert(j);
379 var sc = selected_certs.length;
380 selected_certs[sc] = cert;
381 selected_index[sc] = j;
388 function getSelectedTreeItems() {
389 var ca_tab = document.getElementById("ca_tab");
390 var mine_tab = document.getElementById("mine_tab");
391 var others_tab = document.getElementById("others_tab");
393 if (ca_tab.selected) {
394 items = caTreeView.selection;
395 } else if (mine_tab.selected) {
396 items = userTreeView.selection;
397 } else if (others_tab.selected) {
398 items = emailTreeView.selection;
401 selected_tree_items = [];
403 var tree_item = null;
406 nr = items.getRangeCount();
409 for (let i = 0; i < nr; i++) {
412 items.getRangeAt(i, o1, o2);
415 for (let j = min; j <= max; j++) {
416 if (ca_tab.selected) {
417 tree_item = caTreeView.getTreeItem(j);
418 } else if (mine_tab.selected) {
419 tree_item = userTreeView.getTreeItem(j);
420 } else if (others_tab.selected) {
421 tree_item = emailTreeView.getTreeItem(j);
424 var sc = selected_tree_items.length;
425 selected_tree_items[sc] = tree_item;
426 selected_index[sc] = j;
434 * Returns true if nothing in the given cert tree is selected or if the
435 * selection includes a container. Returns false otherwise.
437 * @param {nsICertTree} certTree
440 function nothingOrContainerSelected(certTree) {
441 var certTreeSelection = certTree.selection;
442 var numSelectionRanges = certTreeSelection.getRangeCount();
444 if (numSelectionRanges == 0) {
448 for (var i = 0; i < numSelectionRanges; i++) {
451 certTreeSelection.getRangeAt(i, o1, o2);
452 var minIndex = o1.value;
453 var maxIndex = o2.value;
454 for (var j = minIndex; j <= maxIndex; j++) {
455 if (certTree.isContainer(j)) {
464 async function promptError(aErrorCode) {
465 if (aErrorCode != Ci.nsIX509CertDB.Success) {
466 let msgName = "pkcs12-unknown-err";
467 switch (aErrorCode) {
468 case Ci.nsIX509CertDB.ERROR_PKCS12_NOSMARTCARD_EXPORT:
469 msgName = "pkcs12-info-no-smartcard-backup";
471 case Ci.nsIX509CertDB.ERROR_PKCS12_RESTORE_FAILED:
472 msgName = "pkcs12-unknown-err-restore";
474 case Ci.nsIX509CertDB.ERROR_PKCS12_BACKUP_FAILED:
475 msgName = "pkcs12-unknown-err-backup";
477 case Ci.nsIX509CertDB.ERROR_PKCS12_CERT_COLLISION:
478 case Ci.nsIX509CertDB.ERROR_PKCS12_DUPLICATE_DATA:
479 msgName = "pkcs12-dup-data";
481 case Ci.nsIX509CertDB.ERROR_BAD_PASSWORD:
482 msgName = "pk11-bad-password";
484 case Ci.nsIX509CertDB.ERROR_DECODE_ERROR:
485 msgName = "pkcs12-decode-err";
490 let [message] = await document.l10n.formatValues([{ id: msgName }]);
491 let prompter = Services.ww.getNewPrompter(window);
492 prompter.alert(null, message);
497 * Enables or disables buttons corresponding to a cert tree depending on what
498 * is selected in the cert tree.
500 * @param {nsICertTree} certTree
501 * @param {Array} idList A list of string identifiers for button elements to
504 function enableButtonsForCertTree(certTree, idList) {
505 let disableButtons = nothingOrContainerSelected(certTree);
507 for (let id of idList) {
508 document.getElementById(id).setAttribute("disabled", disableButtons);
512 function ca_enableButtons() {
519 enableButtonsForCertTree(caTreeView, idList);
522 function mine_enableButtons() {
523 let idList = ["mine_viewButton", "mine_backupButton", "mine_deleteButton"];
524 enableButtonsForCertTree(userTreeView, idList);
527 function email_enableButtons() {
528 let idList = ["email_viewButton", "email_exportButton", "email_deleteButton"];
529 enableButtonsForCertTree(emailTreeView, idList);
532 async function backupCerts() {
534 var numcerts = selected_certs.length;
539 var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
540 let [backupFileDialog, filePkcs12Spec] = await document.l10n.formatValues([
541 { id: "choose-p12-backup-file-dialog" },
542 { id: "file-browse-pkcs12-spec" },
544 fp.init(window.browsingContext, backupFileDialog, Ci.nsIFilePicker.modeSave);
545 fp.appendFilter(filePkcs12Spec, "*.p12");
546 fp.appendFilters(Ci.nsIFilePicker.filterAll);
547 fp.defaultExtension = "p12";
550 rv == Ci.nsIFilePicker.returnOK ||
551 rv == Ci.nsIFilePicker.returnReplace
554 if (certdialogs.setPKCS12FilePassword(window, password)) {
555 let errorCode = certdb.exportPKCS12File(
560 promptError(errorCode);
566 function backupAllCerts() {
567 // Select all rows, then call doBackup()
568 userTreeView.selection.selectAll();
572 function editCerts() {
575 for (let cert of selected_certs) {
576 window.browsingContext.topChromeWindow.openDialog(
577 "chrome://pippki/content/editcacert.xhtml",
579 "chrome,centerscreen,modal",
585 async function restoreCerts() {
586 var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
587 let [restoreFileDialog, filePkcs12Spec, fileCertSpec] =
588 await document.l10n.formatValues([
589 { id: "choose-p12-restore-file-dialog" },
590 { id: "file-browse-pkcs12-spec" },
591 { id: "file-browse-certificate-spec" },
593 fp.init(window.browsingContext, restoreFileDialog, Ci.nsIFilePicker.modeOpen);
594 fp.appendFilter(filePkcs12Spec, "*.p12; *.pfx");
595 fp.appendFilter(fileCertSpec, gCertFileTypes);
596 fp.appendFilters(Ci.nsIFilePicker.filterAll);
598 if (rv != Ci.nsIFilePicker.returnOK) {
602 // If this is an X509 user certificate, import it as one.
604 var isX509FileType = false;
605 var fileTypesList = gCertFileTypes.slice(1).split("; *");
606 for (var type of fileTypesList) {
607 if (fp.file.path.endsWith(type)) {
608 isX509FileType = true;
613 if (isX509FileType) {
615 "@mozilla.org/network/file-input-stream;1"
616 ].createInstance(Ci.nsIFileInputStream);
617 fstream.init(fp.file, -1, 0, 0);
618 let dataString = NetUtil.readInputStreamToString(
623 for (let i = 0; i < dataString.length; i++) {
624 dataArray.push(dataString.charCodeAt(i));
627 let prompter = Services.ww.getNewPrompter(window);
628 let interfaceRequestor = {
633 certdb.importUserCertificate(
639 // Otherwise, assume it's a PKCS12 file and import it that way.
641 let errorCode = Ci.nsIX509CertDB.ERROR_BAD_PASSWORD;
643 errorCode == Ci.nsIX509CertDB.ERROR_BAD_PASSWORD &&
644 certdialogs.getPKCS12FilePassword(window, password)
646 errorCode = certdb.importPKCS12File(fp.file, password.value);
648 errorCode == Ci.nsIX509CertDB.ERROR_BAD_PASSWORD &&
649 !password.value.length
651 // It didn't like empty string password, try no password.
652 errorCode = certdb.importPKCS12File(fp.file, null);
654 promptError(errorCode);
658 var certcache = certdb.getCerts();
659 userTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.USER_CERT);
660 userTreeView.selection.clearSelection();
661 caTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.CA_CERT);
662 caTreeView.selection.clearSelection();
663 enableBackupAllButton();
667 async function exportCerts() {
670 for (let cert of selected_certs) {
671 await exportToFile(window, cert);
676 * Deletes the selected certs in the active tab.
678 function deleteCerts() {
679 getSelectedTreeItems();
680 let numcerts = selected_tree_items.length;
685 const treeViewMap = {
686 mine_tab: userTreeView,
688 others_tab: emailTreeView,
690 let selTab = document.getElementById("certMgrTabbox").selectedItem;
691 let selTabID = selTab.getAttribute("id");
693 if (!(selTabID in treeViewMap)) {
698 deleteConfirmed: false,
700 window.browsingContext.topChromeWindow.openDialog(
701 "chrome://pippki/content/deletecert.xhtml",
703 "chrome,centerscreen,modal",
709 if (retVals.deleteConfirmed) {
710 let treeView = treeViewMap[selTabID];
712 for (let t = numcerts - 1; t >= 0; t--) {
713 treeView.deleteEntryObject(selected_index[t]);
716 selected_tree_items = [];
718 treeView.selection.clearSelection();
719 if (selTabID == "mine_tab") {
720 enableBackupAllButton();
725 function viewCerts() {
728 for (let cert of selected_certs) {
729 viewCertHelper(window, cert);
733 async function addCACerts() {
734 var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
735 let [importCa, fileCertSpec] = await document.l10n.formatValues([
736 { id: "import-ca-certs-prompt" },
737 { id: "file-browse-certificate-spec" },
739 fp.init(window.browsingContext, importCa, Ci.nsIFilePicker.modeOpen);
740 fp.appendFilter(fileCertSpec, gCertFileTypes);
741 fp.appendFilters(Ci.nsIFilePicker.filterAll);
743 if (rv == Ci.nsIFilePicker.returnOK) {
744 certdb.importCertsFromFile(fp.file, Ci.nsIX509Cert.CA_CERT);
745 let certcache = certdb.getCerts();
746 caTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.CA_CERT);
747 caTreeView.selection.clearSelection();
752 async function addEmailCert() {
753 var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
754 let [importEmail, fileCertSpec] = await document.l10n.formatValues([
755 { id: "import-email-cert-prompt" },
756 { id: "file-browse-certificate-spec" },
758 fp.init(window.browsingContext, importEmail, Ci.nsIFilePicker.modeOpen);
759 fp.appendFilter(fileCertSpec, gCertFileTypes);
760 fp.appendFilters(Ci.nsIFilePicker.filterAll);
762 if (rv == Ci.nsIFilePicker.returnOK) {
763 certdb.importCertsFromFile(fp.file, Ci.nsIX509Cert.EMAIL_CERT);
764 var certcache = certdb.getCerts();
765 emailTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.EMAIL_CERT);
766 emailTreeView.selection.clearSelection();
767 caTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.CA_CERT);
768 caTreeView.selection.clearSelection();