mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-09 21:00:42 +02:00
chooseCertificate() currently uses a concatenation of the Common Name of the server cert and the port of the server to allow the user to identify the server requesting client authentication. Unfortunately, this approach is flawed, since it doesn't take into account things like SAN entries, which might be very different from the CN. Using the hostname instead avoids this problem. MozReview-Commit-ID: 6XjGCknWNi9 --HG-- extra : transplant_source : k%10N%7B%E8%A4%9B%C9%9A%23Q%D1%99%D2%A3%C0.%2B%7F%A5
276 lines
10 KiB
JavaScript
276 lines
10 KiB
JavaScript
/* 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]);
|