diff options
Diffstat (limited to 'mobile/android/components/NSSDialogService.js')
-rw-r--r-- | mobile/android/components/NSSDialogService.js | 276 |
1 files changed, 276 insertions, 0 deletions
diff --git a/mobile/android/components/NSSDialogService.js b/mobile/android/components/NSSDialogService.js new file mode 100644 index 000000000..671cc8c35 --- /dev/null +++ b/mobile/android/components/NSSDialogService.js @@ -0,0 +1,276 @@ +/* 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, "'") + .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 = [ + "<big>" + this.getString(aHeading) + "</big>", + ]; + + 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("<br/>"); + } + + return certInfoStrings.join("<br/>"); + }, + + 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} <br/> 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("<br/>"); + }, + + 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("<br/>"); + + 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]); |