/* 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/. */ const Ci = Components.interfaces; const Cu = Components.utils; const Cc = Components.classes; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Prompt", "resource://gre/modules/Prompt.jsm"); // ----------------------------------------------------------------------- // NSS Dialog Service // ----------------------------------------------------------------------- function NSSDialogs() { } NSSDialogs.prototype = { classID: Components.ID("{cbc08081-49b6-4561-9c18-a7707a50bda1}"), QueryInterface: XPCOMUtils.generateQI([Ci.nsICertificateDialogs, Ci.nsIClientAuthDialogs]), /** * Escapes the given input via HTML entity encoding. Used to prevent HTML * injection when the input is to be placed inside an HTML body, but not in * any other context. * * @param {String} input The input to interpret as a plain string. * @returns {String} The escaped input. */ escapeHTML: function(input) { return input.replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'") .replace(/\//g, "/"); }, getString: function(aName) { if (!this.bundle) { this.bundle = Services.strings.createBundle("chrome://browser/locale/pippki.properties"); } return this.bundle.GetStringFromName(aName); }, formatString: function(aName, argList) { if (!this.bundle) { this.bundle = Services.strings.createBundle("chrome://browser/locale/pippki.properties"); } let escapedArgList = Array.from(argList, x => this.escapeHTML(x)); return this.bundle.formatStringFromName(aName, escapedArgList, escapedArgList.length); }, getPrompt: function(aTitle, aText, aButtons) { return new Prompt({ title: aTitle, text: aText, buttons: aButtons, }); }, showPrompt: function(aPrompt) { let response = null; aPrompt.show(function(data) { response = data; }); // Spin this thread while we wait for a result let thread = Services.tm.currentThread; while (response === null) thread.processNextEvent(true); return response; }, confirmDownloadCACert: function(aCtx, aCert, aTrust) { while (true) { let prompt = this.getPrompt(this.getString("downloadCert.title"), this.getString("downloadCert.message1"), [ this.getString("nssdialogs.ok.label"), this.getString("downloadCert.viewCert.label"), this.getString("nssdialogs.cancel.label") ]); prompt.addCheckbox({ id: "trustSSL", label: this.getString("downloadCert.trustSSL"), checked: false }) .addCheckbox({ id: "trustEmail", label: this.getString("downloadCert.trustEmail"), checked: false }) .addCheckbox({ id: "trustSign", label: this.getString("downloadCert.trustObjSign"), checked: false }); let response = this.showPrompt(prompt); // they hit the "view cert" button, so show the cert and try again if (response.button == 1) { this.viewCert(aCtx, aCert); continue; } else if (response.button != 0) { return false; } aTrust.value = Ci.nsIX509CertDB.UNTRUSTED; if (response.trustSSL) aTrust.value |= Ci.nsIX509CertDB.TRUSTED_SSL; if (response.trustEmail) aTrust.value |= Ci.nsIX509CertDB.TRUSTED_EMAIL; if (response.trustSign) aTrust.value |= Ci.nsIX509CertDB.TRUSTED_OBJSIGN; return true; } }, setPKCS12FilePassword: function(aCtx, aPassword) { // this dialog is never shown in Fennec; in Desktop it is shown while backing up a personal // certificate to a file via Preferences->Advanced->Encryption->View Certificates->Your Certificates throw "Unimplemented"; }, getPKCS12FilePassword: function(aCtx, aPassword) { let prompt = this.getPrompt(this.getString("pkcs12.getpassword.title"), this.getString("pkcs12.getpassword.message"), [ this.getString("nssdialogs.ok.label"), this.getString("nssdialogs.cancel.label") ]).addPassword({id: "pw"}); let response = this.showPrompt(prompt); if (response.button != 0) { return false; } aPassword.value = response.pw; return true; }, certInfoSection: function(aHeading, aDataPairs, aTrailingNewline = true) { let certInfoStrings = [ "" + this.getString(aHeading) + "", ]; for (let i = 0; i < aDataPairs.length; i += 2) { let key = aDataPairs[i]; let value = aDataPairs[i + 1]; certInfoStrings.push(this.formatString(key, [value])); } if (aTrailingNewline) { certInfoStrings.push("
"); } return certInfoStrings.join("
"); }, viewCert: function(aCtx, aCert) { let p = this.getPrompt(this.getString("certmgr.title"), "", [ this.getString("nssdialogs.ok.label"), ]); p.addLabel({ label: this.certInfoSection("certmgr.subjectinfo.label", ["certdetail.cn", aCert.commonName, "certdetail.o", aCert.organization, "certdetail.ou", aCert.organizationalUnit, "certdetail.serialnumber", aCert.serialNumber])}) .addLabel({ label: this.certInfoSection("certmgr.issuerinfo.label", ["certdetail.cn", aCert.issuerCommonName, "certdetail.o", aCert.issuerOrganization, "certdetail.ou", aCert.issuerOrganizationUnit])}) .addLabel({ label: this.certInfoSection("certmgr.periodofvalidity.label", ["certdetail.notBefore", aCert.validity.notBeforeLocalDay, "certdetail.notAfter", aCert.validity.notAfterLocalDay])}) .addLabel({ label: this.certInfoSection("certmgr.fingerprints.label", ["certdetail.sha256fingerprint", aCert.sha256Fingerprint, "certdetail.sha1fingerprint", aCert.sha1Fingerprint], false) }); this.showPrompt(p); }, /** * Returns a list of details of the given cert relevant for TLS client * authentication. * * @param {nsIX509Cert} cert Cert to get the details of. * @returns {String}
delimited list of details. */ getCertDetails: function(cert) { let detailLines = [ this.formatString("clientAuthAsk.issuedTo", [cert.subjectName]), this.formatString("clientAuthAsk.serial", [cert.serialNumber]), this.formatString("clientAuthAsk.validityPeriod", [cert.validity.notBeforeLocalTime, cert.validity.notAfterLocalTime]), ]; let keyUsages = cert.keyUsages; if (keyUsages) { detailLines.push(this.formatString("clientAuthAsk.keyUsages", [keyUsages])); } let emailAddresses = cert.getEmailAddresses({}); if (emailAddresses.length > 0) { let joinedAddresses = emailAddresses.join(", "); detailLines.push(this.formatString("clientAuthAsk.emailAddresses", [joinedAddresses])); } detailLines.push(this.formatString("clientAuthAsk.issuedBy", [cert.issuerName])); detailLines.push(this.formatString("clientAuthAsk.storedOn", [cert.tokenName])); return detailLines.join("
"); }, viewCertDetails: function(details) { let p = this.getPrompt(this.getString("clientAuthAsk.message3"), '', [ this.getString("nssdialogs.ok.label") ]); p.addLabel({ label: details }); this.showPrompt(p); }, chooseCertificate: function(ctx, hostname, port, organization, issuerOrg, certList, selectedIndex) { let rememberSetting = Services.prefs.getBoolPref("security.remember_cert_checkbox_default_setting"); let serverRequestedDetails = [ this.formatString("clientAuthAsk.hostnameAndPort", [hostname, port.toString()]), this.formatString("clientAuthAsk.organization", [organization]), this.formatString("clientAuthAsk.issuer", [issuerOrg]), ].join("
"); let certNickList = []; let certDetailsList = []; for (let i = 0; i < certList.length; i++) { let cert = certList.queryElementAt(i, Ci.nsIX509Cert); certNickList.push(this.formatString("clientAuthAsk.nickAndSerial", [cert.nickname, cert.serialNumber])); certDetailsList.push(this.getCertDetails(cert)); } selectedIndex.value = 0; while (true) { let buttons = [ this.getString("nssdialogs.ok.label"), this.getString("clientAuthAsk.viewCert.label"), this.getString("nssdialogs.cancel.label"), ]; let prompt = this.getPrompt(this.getString("clientAuthAsk.title"), this.getString("clientAuthAsk.message1"), buttons) .addLabel({ id: "requestedDetails", label: serverRequestedDetails } ) .addMenulist({ id: "nicknames", label: this.getString("clientAuthAsk.message2"), values: certNickList, selected: selectedIndex.value, }).addCheckbox({ id: "rememberBox", label: this.getString("clientAuthAsk.remember.label"), checked: rememberSetting }); let response = this.showPrompt(prompt); selectedIndex.value = response.nicknames; if (response.button == 1 /* buttons[1] */) { this.viewCertDetails(certDetailsList[selectedIndex.value]); continue; } else if (response.button == 0 /* buttons[0] */) { if (response.rememberBox == true) { let caud = ctx.QueryInterface(Ci.nsIClientAuthUserDecision); if (caud) { caud.rememberClientAuthCertificate = true; } } return true; } return false; } } }; this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NSSDialogs]);