/* -*- 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/. */ "use strict"; const { parse, pemToDER } = ChromeUtils.importESModule( "chrome://global/content/certviewer/certDecoder.mjs" ); /** * @file Implements the functionality of clientauthask.xhtml: a dialog that allows * a user pick a client certificate for TLS client authentication. * @param {object} window.arguments.0 * An Object with the properties: * {String} hostname * The hostname of the server requesting client authentication. * {Array} certArray * Array of certificates the user can choose from * {Object} retVal * Object to set the return values of calling the dialog on. * See ClientAuthAskReturnValues. */ /** * @typedef ClientAuthAskReturnValues * @type {object} * @property {nsIX509Cert} cert * The certificate, if chosen. null otherwise. * @property {number} rememberDuration * Set to Ci.nsIClientAuthRememberService.Once if the decision should * be remembered once, Ci.nsIClientAuthRememberService.Session if the * decision should be remembered for this session, or * Ci.nsIClientAuthRememberService.Permanent if the decision should * be remembered permanently. */ /** * The array of certs the user can choose from. * * @type {Array} */ var certArray; /** * The checkbox storing whether the user wants to remember the selected cert. * * @type {HTMLInputElement} Element checkbox, has to have |checked| property. */ var args; async function onLoad() { let rememberSetting = Services.prefs.getIntPref( "security.client_auth_certificate_default_remember_setting" ); document.getElementById("rememberSetting").value = rememberSetting >= 0 && rememberSetting <= 2 ? rememberSetting : 2; let propBag = window.arguments[0] .QueryInterface(Ci.nsIWritablePropertyBag2) .QueryInterface(Ci.nsIWritablePropertyBag); args = {}; for (let prop of propBag.enumerator) { args[prop.name] = prop.value; } certArray = args.certArray; document.l10n.setAttributes( document.getElementById("clientAuthSiteIdentification"), "client-auth-site-identification", { hostname: args.hostname } ); let selectElement = document.getElementById("nicknames"); for (let i = 0; i < certArray.length; i++) { let menuItemNode = document.createXULElement("menuitem"); let cert = certArray[i]; let nickAndSerial = `${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); document .getElementById("nicknames") .addEventListener("command", () => onCertSelected()); 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[index]; document.l10n.setAttributes( document.getElementById("clientAuthCertDetailsIssuedTo"), "client-auth-cert-details-issued-to", { issuedTo: cert.subjectName } ); document.l10n.setAttributes( document.getElementById("clientAuthCertDetailsSerialNumber"), "client-auth-cert-details-serial-number", { serialNumber: cert.serialNumber } ); const formatter = new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "long", }); document.l10n.setAttributes( document.getElementById("clientAuthCertDetailsValidityPeriod"), "client-auth-cert-details-validity-period", { notBefore: formatter.format(new Date(cert.validity.notBefore / 1000)), notAfter: formatter.format(new Date(cert.validity.notAfter / 1000)), } ); let parsedCert = await parse(pemToDER(cert.getBase64DERString())); let keyUsages = parsedCert.ext.keyUsages; let keyUsagesJoined = keyUsages && keyUsages.purposes.length ? keyUsages.purposes.join(", ") : ""; document.l10n.setAttributes( document.getElementById("clientAuthCertDetailsKeyUsages"), "client-auth-cert-details-key-usages", { keyUsages: keyUsagesJoined } ); let emailAddresses = cert.getEmailAddresses(); let emailAddressesJoined = emailAddresses.length ? emailAddresses.join(", ") : ""; document.l10n.setAttributes( document.getElementById("clientAuthCertDetailsEmailAddresses"), "client-auth-cert-details-email-addresses", { emailAddresses: emailAddressesJoined } ); document.l10n.setAttributes( document.getElementById("clientAuthCertDetailsIssuedBy"), "client-auth-cert-details-issued-by", { issuedBy: cert.issuerName } ); document.l10n.setAttributes( document.getElementById("clientAuthCertDetailsStoredOn"), "client-auth-cert-details-stored-on", { storedOn: cert.tokenName } ); } async function onCertSelected() { await setDetails(); } function getRememberSetting() { return parseInt(document.getElementById("rememberSetting").value); } function doOK() { let { retVals } = args; let index = parseInt(document.getElementById("nicknames").value); let cert = certArray[index]; retVals.cert = cert; retVals.rememberDuration = getRememberSetting(); } function doCancel() { let { retVals } = args; retVals.cert = null; retVals.rememberDuration = getRememberSetting(); } window.addEventListener("load", () => onLoad());