зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1401466 - make the client auth certificate selection dialog tab modal r=jschanck,necko-reviewers,bolsson,kershaw,valentin
Previously, the client authentication certificate selection dialog could show up unexpectedly. Because it was modal, it would prevent user interaction with the browser. It could even get in a state where the dialog couldn't be interacted with, and neither could anything else, so the entire browser would be locked and the user would have to quit and restart. This patch associates a top-level outer content window ID (called "browserId" in networking code) with each NSSSocketControl. When a peer asks for a client authentication certificate, the NSSSocketControl can use the ID to find the relevant tab and open a tab-modal dialog, which allows other browser UI to be interacted with. Some loads cannot be associated with browser tabs, and so the implementation falls back to opening a window-modal dialog on the most recently active window. This is still better than the previous implementation, since the dialog is connected to a window rather than being its own separate dialog. Differential Revision: https://phabricator.services.mozilla.com/D183775
This commit is contained in:
Родитель
c3b462663f
Коммит
101100dc72
|
@ -21,34 +21,28 @@ let handshakeDone = false;
|
|||
let expectingChooseCertificate = false;
|
||||
let chooseCertificateCalled = false;
|
||||
|
||||
const clientAuthDialogs = {
|
||||
chooseCertificate(
|
||||
hostname,
|
||||
port,
|
||||
organization,
|
||||
issuerOrg,
|
||||
certList,
|
||||
selectedIndex,
|
||||
rememberClientAuthCertificate
|
||||
) {
|
||||
const clientAuthDialogService = {
|
||||
chooseCertificate(hostname, certArray, loadContext, callback) {
|
||||
ok(
|
||||
expectingChooseCertificate,
|
||||
`${
|
||||
expectingChooseCertificate ? "" : "not "
|
||||
}expecting chooseCertificate to be called`
|
||||
);
|
||||
is(certList.length, 1, "should have only one client certificate available");
|
||||
selectedIndex.value = 0;
|
||||
rememberClientAuthCertificate.value = false;
|
||||
is(
|
||||
certArray.length,
|
||||
1,
|
||||
"should have only one client certificate available"
|
||||
);
|
||||
ok(
|
||||
!chooseCertificateCalled,
|
||||
"chooseCertificate should only be called once"
|
||||
);
|
||||
chooseCertificateCalled = true;
|
||||
return true;
|
||||
callback.certificateChosen(certArray[0], false);
|
||||
},
|
||||
|
||||
QueryInterface: ChromeUtils.generateQI(["nsIClientAuthDialogs"]),
|
||||
QueryInterface: ChromeUtils.generateQI(["nsIClientAuthDialogService"]),
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -164,9 +158,9 @@ add_setup(async function () {
|
|||
],
|
||||
});
|
||||
|
||||
let clientAuthDialogsCID = MockRegistrar.register(
|
||||
"@mozilla.org/nsClientAuthDialogs;1",
|
||||
clientAuthDialogs
|
||||
let clientAuthDialogServiceCID = MockRegistrar.register(
|
||||
"@mozilla.org/security/ClientAuthDialogService;1",
|
||||
clientAuthDialogService
|
||||
);
|
||||
|
||||
let cert = getTestServerCertificate();
|
||||
|
@ -191,7 +185,7 @@ add_setup(async function () {
|
|||
|
||||
registerCleanupFunction(async function () {
|
||||
await PlacesUtils.history.clear();
|
||||
MockRegistrar.unregister(clientAuthDialogsCID);
|
||||
MockRegistrar.unregister(clientAuthDialogServiceCID);
|
||||
certOverrideService.clearValidityOverride("localhost", server.port, {});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -781,7 +781,7 @@ BackgroundParentImpl::AllocPSelectTLSClientAuthCertParent(
|
|||
const nsACString& aHostName, const OriginAttributes& aOriginAttributes,
|
||||
const int32_t& aPort, const uint32_t& aProviderFlags,
|
||||
const uint32_t& aProviderTlsFlags, const ByteArray& aServerCertBytes,
|
||||
const nsTArray<ByteArray>& aCANames) {
|
||||
const nsTArray<ByteArray>& aCANames, const uint64_t& aBrowserId) {
|
||||
RefPtr<mozilla::psm::SelectTLSClientAuthCertParent> parent =
|
||||
new mozilla::psm::SelectTLSClientAuthCertParent();
|
||||
return parent.forget();
|
||||
|
@ -792,12 +792,14 @@ BackgroundParentImpl::RecvPSelectTLSClientAuthCertConstructor(
|
|||
PSelectTLSClientAuthCertParent* actor, const nsACString& aHostName,
|
||||
const OriginAttributes& aOriginAttributes, const int32_t& aPort,
|
||||
const uint32_t& aProviderFlags, const uint32_t& aProviderTlsFlags,
|
||||
const ByteArray& aServerCertBytes, nsTArray<ByteArray>&& aCANames) {
|
||||
const ByteArray& aServerCertBytes, nsTArray<ByteArray>&& aCANames,
|
||||
const uint64_t& aBrowserId) {
|
||||
mozilla::psm::SelectTLSClientAuthCertParent* selectTLSClientAuthCertParent =
|
||||
static_cast<mozilla::psm::SelectTLSClientAuthCertParent*>(actor);
|
||||
if (!selectTLSClientAuthCertParent->Dispatch(
|
||||
aHostName, aOriginAttributes, aPort, aProviderFlags,
|
||||
aProviderTlsFlags, aServerCertBytes, std::move(aCANames))) {
|
||||
aProviderTlsFlags, aServerCertBytes, std::move(aCANames),
|
||||
aBrowserId)) {
|
||||
return IPC_FAIL_NO_REASON(this);
|
||||
}
|
||||
return IPC_OK();
|
||||
|
|
|
@ -220,13 +220,13 @@ class BackgroundParentImpl : public PBackgroundParent {
|
|||
const nsACString& aHostName, const OriginAttributes& aOriginAttributes,
|
||||
const int32_t& aPort, const uint32_t& aProviderFlags,
|
||||
const uint32_t& aProviderTlsFlags, const ByteArray& aServerCertBytes,
|
||||
const nsTArray<ByteArray>& aCANames) override;
|
||||
const nsTArray<ByteArray>& aCANames, const uint64_t& aBrowserId) override;
|
||||
virtual mozilla::ipc::IPCResult RecvPSelectTLSClientAuthCertConstructor(
|
||||
PSelectTLSClientAuthCertParent* actor, const nsACString& aHostName,
|
||||
const OriginAttributes& aOriginAttributes, const int32_t& aPort,
|
||||
const uint32_t& aProviderFlags, const uint32_t& aProviderTlsFlags,
|
||||
const ByteArray& aServerCertBytes,
|
||||
nsTArray<ByteArray>&& aCANames) override;
|
||||
const ByteArray& aServerCertBytes, nsTArray<ByteArray>&& aCANames,
|
||||
const uint64_t& aBrowserId) override;
|
||||
|
||||
PBroadcastChannelParent* AllocPBroadcastChannelParent(
|
||||
const PrincipalInfo& aPrincipalInfo, const nsACString& aOrigin,
|
||||
|
|
|
@ -293,7 +293,8 @@ parent:
|
|||
uint32_t aProviderFlags,
|
||||
uint32_t aProviderTlsFlags,
|
||||
ByteArray aServerCertBytes,
|
||||
ByteArray[] aCANames);
|
||||
ByteArray[] aCANames,
|
||||
uint64_t aBrowserId);
|
||||
|
||||
async PVerifySSLServerCert(ByteArray[] aPeerCertChain,
|
||||
nsCString aHostName,
|
||||
|
|
|
@ -181,5 +181,12 @@ FuzzySocketControl::AsyncGetSecurityInfo(JSContext* aCx,
|
|||
|
||||
NS_IMETHODIMP FuzzySocketControl::Claim() { return NS_OK; }
|
||||
|
||||
NS_IMETHODIMP FuzzySocketControl::SetBrowserId(uint64_t) { return NS_OK; }
|
||||
|
||||
NS_IMETHODIMP FuzzySocketControl::GetBrowserId(uint64_t*) {
|
||||
MOZ_CRASH("Unused");
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
} // namespace net
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -560,6 +560,13 @@ nsresult nsHttpConnection::Activate(nsAHttpTransaction* trans, uint32_t caps,
|
|||
// take ownership of the transaction
|
||||
mTransaction = trans;
|
||||
|
||||
nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
|
||||
if (NS_SUCCEEDED(mSocketTransport->GetTlsSocketControl(
|
||||
getter_AddRefs(tlsSocketControl))) &&
|
||||
tlsSocketControl) {
|
||||
tlsSocketControl->SetBrowserId(mTransaction->BrowserId());
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!mIdleMonitoring, "Activating a connection with an Idle Monitor");
|
||||
mIdleMonitoring = false;
|
||||
|
||||
|
|
|
@ -126,7 +126,7 @@ function storeCertOverride(port, cert) {
|
|||
}
|
||||
|
||||
function startClient(port, sendClientCert, expectingAlert, tlsVersion) {
|
||||
gClientAuthDialogs.selectCertificate = sendClientCert;
|
||||
gClientAuthDialogService.selectCertificate = sendClientCert;
|
||||
let SSL_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE;
|
||||
let SSL_ERROR_BAD_CERT_ALERT = SSL_ERROR_BASE + 17;
|
||||
let SSL_ERROR_RX_CERTIFICATE_REQUIRED_ALERT = SSL_ERROR_BASE + 181;
|
||||
|
@ -210,56 +210,30 @@ function startClient(port, sendClientCert, expectingAlert, tlsVersion) {
|
|||
}
|
||||
|
||||
// Replace the UI dialog that prompts the user to pick a client certificate.
|
||||
const gClientAuthDialogs = {
|
||||
const gClientAuthDialogService = {
|
||||
_selectCertificate: false,
|
||||
|
||||
set selectCertificate(value) {
|
||||
this._selectCertificate = value;
|
||||
},
|
||||
|
||||
chooseCertificate(
|
||||
hostname,
|
||||
port,
|
||||
organization,
|
||||
issuerOrg,
|
||||
certList,
|
||||
selectedIndex,
|
||||
rememberClientAuthCertificate
|
||||
) {
|
||||
rememberClientAuthCertificate.value = false;
|
||||
chooseCertificate(hostname, certArray, loadContext, callback) {
|
||||
if (this._selectCertificate) {
|
||||
selectedIndex.value = 0;
|
||||
return true;
|
||||
callback.certificateChosen(certArray[0], false);
|
||||
} else {
|
||||
callback.certificateChose(null, false);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIClientAuthDialogs]),
|
||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIClientAuthDialogService]),
|
||||
};
|
||||
|
||||
const ClientAuthDialogsContractID = "@mozilla.org/nsClientAuthDialogs;1";
|
||||
// On all platforms but Android, this component already exists, so this replaces
|
||||
// it. On Android, the component does not exist, so this registers it.
|
||||
if (AppConstants.platform != "android") {
|
||||
MockRegistrar.register(ClientAuthDialogsContractID, gClientAuthDialogs);
|
||||
} else {
|
||||
const factory = {
|
||||
createInstance(iid) {
|
||||
return gClientAuthDialogs.QueryInterface(iid);
|
||||
},
|
||||
|
||||
QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
|
||||
};
|
||||
const Cm = Components.manager;
|
||||
const registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
|
||||
const cid = Services.uuid.generateUUID();
|
||||
registrar.registerFactory(
|
||||
cid,
|
||||
"A Mock for " + ClientAuthDialogsContractID,
|
||||
ClientAuthDialogsContractID,
|
||||
factory
|
||||
);
|
||||
}
|
||||
const ClientAuthDialogServiceContractID =
|
||||
"@mozilla.org/security/ClientAuthDialogService;1";
|
||||
MockRegistrar.register(
|
||||
ClientAuthDialogServiceContractID,
|
||||
gClientAuthDialogService
|
||||
);
|
||||
|
||||
const tests = [
|
||||
{
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
# Any copyright is dedicated to the Public Domain.
|
||||
# http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
import fluent.syntax.ast as FTL
|
||||
from fluent.migrate.helpers import VARIABLE_REFERENCE
|
||||
from fluent.migrate import COPY, REPLACE
|
||||
|
||||
|
||||
def migrate(ctx):
|
||||
"""Bug 1401466 - Migrate some client authentication dialog strings from pippki.properties to pippki.ftl, part {index}"""
|
||||
|
||||
pippki_ftl = "security/manager/security/pippki/pippki.ftl"
|
||||
pippki_properties = "security/manager/chrome/pippki/pippki.properties"
|
||||
|
||||
ctx.add_transforms(
|
||||
pippki_ftl,
|
||||
pippki_ftl,
|
||||
[
|
||||
FTL.Message(
|
||||
id=FTL.Identifier("client-auth-cert-details-validity-period"),
|
||||
value=REPLACE(
|
||||
pippki_properties,
|
||||
"clientAuthValidityPeriod",
|
||||
{
|
||||
"%1$S": VARIABLE_REFERENCE("notBefore"),
|
||||
"%2$S": VARIABLE_REFERENCE("notAfter"),
|
||||
},
|
||||
),
|
||||
),
|
||||
FTL.Message(
|
||||
id=FTL.Identifier("client-auth-cert-remember-box"),
|
||||
attributes=[
|
||||
FTL.Attribute(
|
||||
id=FTL.Identifier("label"),
|
||||
value=COPY(
|
||||
pippki_properties,
|
||||
"clientAuthRemember",
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
for ftl_name, properties_name, variable_name in [
|
||||
("issued-to", "IssuedTo", "issuedTo"),
|
||||
("serial-number", "Serial", "serialNumber"),
|
||||
("key-usages", "KeyUsages", "keyUsages"),
|
||||
("email-addresses", "EmailAddresses", "emailAddresses"),
|
||||
("issued-by", "IssuedBy", "issuedBy"),
|
||||
("stored-on", "StoredOn", "storedOn"),
|
||||
]:
|
||||
properties_id = f"clientAuth{properties_name}"
|
||||
ctx.add_transforms(
|
||||
pippki_ftl,
|
||||
pippki_ftl,
|
||||
[
|
||||
FTL.Message(
|
||||
id=FTL.Identifier(f"client-auth-cert-details-{ftl_name}"),
|
||||
value=REPLACE(
|
||||
pippki_properties,
|
||||
properties_id,
|
||||
{
|
||||
"%1$S": VARIABLE_REFERENCE(variable_name),
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
|
@ -11,47 +11,6 @@ unnamedCA=Certificate Authority (unnamed)
|
|||
# PKCS#12 file dialogs
|
||||
getPKCS12FilePasswordMessage=Please enter the password that was used to encrypt this certificate backup:
|
||||
|
||||
# Client auth
|
||||
clientAuthRemember=Remember this decision
|
||||
# LOCALIZATION NOTE(clientAuthNickAndSerial): Represents a single cert when the
|
||||
# user is choosing from a list of certificates.
|
||||
# %1$S is the nickname of the cert.
|
||||
# %2$S is the serial number of the cert in AA:BB:CC hex format.
|
||||
clientAuthNickAndSerial=%1$S [%2$S]
|
||||
# LOCALIZATION NOTE(clientAuthHostnameAndPort):
|
||||
# %1$S is the hostname of the server.
|
||||
# %2$S is the port of the server.
|
||||
clientAuthHostnameAndPort=%1$S:%2$S
|
||||
# LOCALIZATION NOTE(clientAuthMessage1): %S is the Organization of the server
|
||||
# cert.
|
||||
clientAuthMessage1=Organization: “%S”
|
||||
# LOCALIZATION NOTE(clientAuthMessage2): %S is the Organization of the issuer
|
||||
# cert of the server cert.
|
||||
clientAuthMessage2=Issued Under: “%S”
|
||||
# LOCALIZATION NOTE(clientAuthIssuedTo): %1$S is the Distinguished Name of the
|
||||
# currently selected client cert, such as "CN=John Doe,OU=Example" (without
|
||||
# quotes).
|
||||
clientAuthIssuedTo=Issued to: %1$S
|
||||
# LOCALIZATION NOTE(clientAuthSerial): %1$S is the serial number of the selected
|
||||
# cert in AA:BB:CC hex format.
|
||||
clientAuthSerial=Serial number: %1$S
|
||||
# LOCALIZATION NOTE(clientAuthValidityPeriod):
|
||||
# %1$S is the already localized notBefore date of the selected cert.
|
||||
# %2$S is the already localized notAfter date of the selected cert.
|
||||
clientAuthValidityPeriod=Valid from %1$S to %2$S
|
||||
# LOCALIZATION NOTE(clientAuthKeyUsages): %1$S is a comma separated list of
|
||||
# already localized key usages the selected cert is valid for.
|
||||
clientAuthKeyUsages=Key Usages: %1$S
|
||||
# LOCALIZATION NOTE(clientAuthEmailAddresses): %1$S is a comma separated list of
|
||||
# e-mail addresses the selected cert is valid for.
|
||||
clientAuthEmailAddresses=Email addresses: %1$S
|
||||
# LOCALIZATION NOTE(clientAuthIssuedBy): %1$S is the Distinguished Name of the
|
||||
# cert which issued the selected cert.
|
||||
clientAuthIssuedBy=Issued by: %1$S
|
||||
# LOCALIZATION NOTE(clientAuthStoredOn): %1$S is the name of the PKCS #11 token
|
||||
# the selected cert is stored on.
|
||||
clientAuthStoredOn=Stored on: %1$S
|
||||
|
||||
# Page Info
|
||||
pageInfo_NoEncryption=Connection Not Encrypted
|
||||
pageInfo_Privacy_None1=The website %S does not support encryption for the page you are viewing.
|
||||
|
|
|
@ -53,13 +53,41 @@ download-cert-view-cert =
|
|||
.label = View
|
||||
download-cert-view-text = Examine CA certificate
|
||||
|
||||
## Client Authorization Ask dialog
|
||||
## Client Authentication Ask dialog
|
||||
|
||||
client-auth-window =
|
||||
.title = User Identification Request
|
||||
client-auth-site-description = This site has requested that you identify yourself with a certificate:
|
||||
client-auth-choose-cert = Choose a certificate to present as identification:
|
||||
client-auth-send-no-certificate =
|
||||
.label = Don’t send a certificate
|
||||
|
||||
# Variables:
|
||||
# $hostname (String) - The domain name of the site requesting the client authentication certificate
|
||||
client-auth-site-identification = “{ $hostname }” has requested that you identify yourself with a certificate:
|
||||
client-auth-cert-details = Details of selected certificate:
|
||||
# Variables:
|
||||
# $issuedTo (String) - The subject common name of the currently-selected client authentication certificate
|
||||
client-auth-cert-details-issued-to = Issued to: { $issuedTo }
|
||||
# Variables:
|
||||
# $serialNumber (String) - The serial number of the certificate (hexadecimal of the form "AA:BB:...")
|
||||
client-auth-cert-details-serial-number = Serial number: { $serialNumber }
|
||||
# Variables:
|
||||
# $notBefore (String) - The date before which the certificate is not valid (e.g. Apr 21, 2023, 1:47:53 PM UTC)
|
||||
# $notAfter (String) - The date after which the certificate is not valid
|
||||
client-auth-cert-details-validity-period = Valid from { $notBefore } to { $notAfter }
|
||||
# Variables:
|
||||
# $keyUsages (String) - A list of already-localized key usages for which the certificate may be used
|
||||
client-auth-cert-details-key-usages = Key usages: { $keyUsages }
|
||||
# Variables:
|
||||
# $emailAddresses (String) - A list of email addresses present in the certificate
|
||||
client-auth-cert-details-email-addresses = Email addresses: { $emailAddresses }
|
||||
# Variables:
|
||||
# $issuedBy (String) - The issuer common name of the certificate
|
||||
client-auth-cert-details-issued-by = Issued by: { $issuedBy }
|
||||
# Variables:
|
||||
# $storedOn (String) - The name of the token holding the certificate (for example, "OS Client Cert Token (Modern)")
|
||||
client-auth-cert-details-stored-on = Stored on: { $storedOn }
|
||||
client-auth-cert-remember-box =
|
||||
.label = Remember this decision
|
||||
|
||||
## Set password (p12) dialog
|
||||
|
||||
|
|
|
@ -9,9 +9,6 @@ Classes = [
|
|||
'cid': '{518e071f-1dd2-11b2-937e-c45f14def778}',
|
||||
'contract_ids': [
|
||||
'@mozilla.org/nsCertificateDialogs;1',
|
||||
'@mozilla.org/nsClientAuthDialogs;1',
|
||||
'@mozilla.org/nsGeneratingKeypairInfoDialogs;1',
|
||||
'@mozilla.org/nsTokenDialogs;1',
|
||||
'@mozilla.org/nsTokenPasswordDialogs;1',
|
||||
],
|
||||
'type': 'nsNSSDialogs',
|
||||
|
|
|
@ -35,8 +35,7 @@ nsNSSDialogs::nsNSSDialogs() = default;
|
|||
|
||||
nsNSSDialogs::~nsNSSDialogs() = default;
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsNSSDialogs, nsITokenPasswordDialogs, nsICertificateDialogs,
|
||||
nsIClientAuthDialogs)
|
||||
NS_IMPL_ISUPPORTS(nsNSSDialogs, nsITokenPasswordDialogs, nsICertificateDialogs)
|
||||
|
||||
nsresult nsNSSDialogs::Init() {
|
||||
nsresult rv;
|
||||
|
@ -154,103 +153,6 @@ nsNSSDialogs::ConfirmDownloadCACert(nsIInterfaceRequestor* ctx,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNSSDialogs::ChooseCertificate(const nsACString& hostname, int32_t port,
|
||||
const nsACString& organization,
|
||||
const nsACString& issuerOrg, nsIArray* certList,
|
||||
/*out*/ uint32_t* selectedIndex,
|
||||
/*out*/ bool* rememberClientAuthCertificate,
|
||||
/*out*/ bool* certificateChosen) {
|
||||
NS_ENSURE_ARG_POINTER(certList);
|
||||
NS_ENSURE_ARG_POINTER(selectedIndex);
|
||||
NS_ENSURE_ARG_POINTER(rememberClientAuthCertificate);
|
||||
NS_ENSURE_ARG_POINTER(certificateChosen);
|
||||
|
||||
*certificateChosen = false;
|
||||
*rememberClientAuthCertificate = false;
|
||||
|
||||
nsCOMPtr<nsIMutableArray> argArray = nsArrayBase::Create();
|
||||
if (!argArray) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIWritableVariant> hostnameVariant = new nsVariant();
|
||||
nsresult rv = hostnameVariant->SetAsAUTF8String(hostname);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
rv = argArray->AppendElement(hostnameVariant);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIWritableVariant> organizationVariant = new nsVariant();
|
||||
rv = organizationVariant->SetAsAUTF8String(organization);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
rv = argArray->AppendElement(organizationVariant);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIWritableVariant> issuerOrgVariant = new nsVariant();
|
||||
rv = issuerOrgVariant->SetAsAUTF8String(issuerOrg);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
rv = argArray->AppendElement(issuerOrgVariant);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIWritableVariant> portVariant = new nsVariant();
|
||||
rv = portVariant->SetAsInt32(port);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
rv = argArray->AppendElement(portVariant);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = argArray->AppendElement(certList);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIWritablePropertyBag2> retVals = new nsHashPropertyBag();
|
||||
rv = argArray->AppendElement(retVals);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = nsNSSDialogHelper::openDialog(
|
||||
nullptr, "chrome://pippki/content/clientauthask.xhtml", argArray);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = retVals->GetPropertyAsBool(u"rememberSelection"_ns,
|
||||
rememberClientAuthCertificate);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = retVals->GetPropertyAsBool(u"certChosen"_ns, certificateChosen);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
if (*certificateChosen) {
|
||||
rv = retVals->GetPropertyAsUint32(u"selectedIndex"_ns, selectedIndex);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNSSDialogs::SetPKCS12FilePassword(nsIInterfaceRequestor* ctx,
|
||||
/*out*/ nsAString& password,
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsICertificateDialogs.h"
|
||||
#include "nsIClientAuthDialogs.h"
|
||||
#include "nsIStringBundle.h"
|
||||
#include "nsITokenPasswordDialogs.h"
|
||||
|
||||
|
@ -21,13 +20,11 @@
|
|||
}
|
||||
|
||||
class nsNSSDialogs : public nsICertificateDialogs,
|
||||
public nsIClientAuthDialogs,
|
||||
public nsITokenPasswordDialogs {
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSITOKENPASSWORDDIALOGS
|
||||
NS_DECL_NSICERTIFICATEDIALOGS
|
||||
NS_DECL_NSICLIENTAUTHDIALOGS
|
||||
nsNSSDialogs();
|
||||
|
||||
nsresult Init();
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/* 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/. */
|
||||
|
||||
:root {
|
||||
min-width: 48em;
|
||||
}
|
||||
|
||||
.important {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.details {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
|
@ -13,49 +13,34 @@ const { parse, pemToDER } = ChromeUtils.importESModule(
|
|||
/**
|
||||
* @file Implements the functionality of clientauthask.xhtml: a dialog that allows
|
||||
* a user pick a client certificate for TLS client authentication.
|
||||
* @param {string} window.arguments.0
|
||||
* The hostname of the server requesting client authentication.
|
||||
* @param {string} window.arguments.1
|
||||
* The Organization of the server cert.
|
||||
* @param {string} window.arguments.2
|
||||
* The Organization of the issuer of the server cert.
|
||||
* @param {number} window.arguments.3
|
||||
* The port of the server.
|
||||
* @param {nsISupports} window.arguments.4
|
||||
* List of certificates the user can choose from, queryable to
|
||||
* nsIArray<nsIX509Cert>.
|
||||
* @param {nsISupports} window.arguments.5
|
||||
* Object to set the return values of calling the dialog on, queryable
|
||||
* to the underlying type of ClientAuthAskReturnValues.
|
||||
* @param {object} window.arguments.0
|
||||
* An Object with the properties:
|
||||
* {String} hostname
|
||||
* The hostname of the server requesting client authentication.
|
||||
* {Array<nsIX509Cert>} 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 {nsIWritablePropertyBag2}
|
||||
* @property {boolean} certChosen
|
||||
* Set to true if the user chose a cert and accepted the dialog, false
|
||||
* otherwise.
|
||||
* @property {boolean} rememberSelection
|
||||
* @type {object}
|
||||
* @property {nsIX509Cert} cert
|
||||
* The certificate, if chosen. null otherwise.
|
||||
* @property {boolean} rememberDecision
|
||||
* Set to true if the user wanted their cert selection to be
|
||||
* remembered, false otherwise.
|
||||
* @property {number} selectedIndex
|
||||
* The index the chosen cert is at for the given cert list. Undefined
|
||||
* value if |certChosen| is not true.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The pippki <stringbundle> element.
|
||||
*
|
||||
* @type {stringbundle}
|
||||
* @see {@link toolkit/content/widgets/stringbundle.js}
|
||||
*/
|
||||
var bundle;
|
||||
/**
|
||||
* The array of certs the user can choose from.
|
||||
*
|
||||
* @type {nsIArray<nsIX509Cert>}
|
||||
* @type {Array<nsIX509Cert>}
|
||||
*/
|
||||
var certArray;
|
||||
|
||||
/**
|
||||
* The checkbox storing whether the user wants to remember the selected cert.
|
||||
*
|
||||
|
@ -64,40 +49,25 @@ var certArray;
|
|||
var rememberBox;
|
||||
|
||||
async function onLoad() {
|
||||
bundle = document.getElementById("pippki_bundle");
|
||||
let rememberSetting = Services.prefs.getBoolPref(
|
||||
"security.remember_cert_checkbox_default_setting"
|
||||
);
|
||||
|
||||
rememberBox = document.getElementById("rememberBox");
|
||||
rememberBox.label = bundle.getString("clientAuthRemember");
|
||||
rememberBox.checked = rememberSetting;
|
||||
|
||||
let hostname = window.arguments[0];
|
||||
let org = window.arguments[1];
|
||||
let issuerOrg = window.arguments[2];
|
||||
let port = window.arguments[3];
|
||||
let formattedOrg = bundle.getFormattedString("clientAuthMessage1", [org]);
|
||||
let formattedIssuerOrg = bundle.getFormattedString("clientAuthMessage2", [
|
||||
issuerOrg,
|
||||
]);
|
||||
let formattedHostnameAndPort = bundle.getFormattedString(
|
||||
"clientAuthHostnameAndPort",
|
||||
[hostname, port.toString()]
|
||||
certArray = window.arguments[0].certArray;
|
||||
|
||||
document.l10n.setAttributes(
|
||||
document.getElementById("clientAuthSiteIdentification"),
|
||||
"client-auth-site-identification",
|
||||
{ hostname: window.arguments[0].hostname }
|
||||
);
|
||||
setText("hostname", formattedHostnameAndPort);
|
||||
setText("organization", formattedOrg);
|
||||
setText("issuer", formattedIssuerOrg);
|
||||
|
||||
let selectElement = document.getElementById("nicknames");
|
||||
certArray = window.arguments[4].QueryInterface(Ci.nsIArray);
|
||||
for (let i = 0; i < certArray.length; i++) {
|
||||
let menuItemNode = document.createXULElement("menuitem");
|
||||
let cert = certArray.queryElementAt(i, Ci.nsIX509Cert);
|
||||
let nickAndSerial = bundle.getFormattedString("clientAuthNickAndSerial", [
|
||||
cert.displayName,
|
||||
cert.serialNumber,
|
||||
]);
|
||||
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);
|
||||
|
@ -121,42 +91,56 @@ async function onLoad() {
|
|||
*/
|
||||
async function setDetails() {
|
||||
let index = parseInt(document.getElementById("nicknames").value);
|
||||
let cert = certArray.queryElementAt(index, Ci.nsIX509Cert);
|
||||
|
||||
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",
|
||||
});
|
||||
let detailLines = [
|
||||
bundle.getFormattedString("clientAuthIssuedTo", [cert.subjectName]),
|
||||
bundle.getFormattedString("clientAuthSerial", [cert.serialNumber]),
|
||||
bundle.getFormattedString("clientAuthValidityPeriod", [
|
||||
formatter.format(new Date(cert.validity.notBefore / 1000)),
|
||||
formatter.format(new Date(cert.validity.notAfter / 1000)),
|
||||
]),
|
||||
];
|
||||
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;
|
||||
if (keyUsages && keyUsages.purposes.length) {
|
||||
detailLines.push(
|
||||
bundle.getFormattedString("clientAuthKeyUsages", [keyUsages.purposes])
|
||||
document.l10n.setAttributes(
|
||||
document.getElementById("clientAuthCertDetailsKeyUsages"),
|
||||
"client-auth-cert-details-key-usages",
|
||||
{ keyUsages: keyUsages.purposes.join(", ") }
|
||||
);
|
||||
}
|
||||
let emailAddresses = cert.getEmailAddresses();
|
||||
if (emailAddresses.length) {
|
||||
let joinedAddresses = emailAddresses.join(", ");
|
||||
detailLines.push(
|
||||
bundle.getFormattedString("clientAuthEmailAddresses", [joinedAddresses])
|
||||
document.l10n.setAttributes(
|
||||
document.getElementById("clientAuthCertDetailsEmailAddresses"),
|
||||
"client-auth-cert-details-email-addresses",
|
||||
{ emailAddresses: emailAddresses.join(", ") }
|
||||
);
|
||||
}
|
||||
detailLines.push(
|
||||
bundle.getFormattedString("clientAuthIssuedBy", [cert.issuerName])
|
||||
document.l10n.setAttributes(
|
||||
document.getElementById("clientAuthCertDetailsIssuedBy"),
|
||||
"client-auth-cert-details-issued-by",
|
||||
{ issuedBy: cert.issuerName }
|
||||
);
|
||||
detailLines.push(
|
||||
bundle.getFormattedString("clientAuthStoredOn", [cert.tokenName])
|
||||
document.l10n.setAttributes(
|
||||
document.getElementById("clientAuthCertDetailsStoredOn"),
|
||||
"client-auth-cert-details-stored-on",
|
||||
{ storedOn: cert.tokenName }
|
||||
);
|
||||
|
||||
document.getElementById("details").value = detailLines.join("\n");
|
||||
}
|
||||
|
||||
async function onCertSelected() {
|
||||
|
@ -164,15 +148,15 @@ async function onCertSelected() {
|
|||
}
|
||||
|
||||
function doOK() {
|
||||
let retVals = window.arguments[5].QueryInterface(Ci.nsIWritablePropertyBag2);
|
||||
retVals.setPropertyAsBool("certChosen", true);
|
||||
let { retVals } = window.arguments[0];
|
||||
let index = parseInt(document.getElementById("nicknames").value);
|
||||
retVals.setPropertyAsUint32("selectedIndex", index);
|
||||
retVals.setPropertyAsBool("rememberSelection", rememberBox.checked);
|
||||
let cert = certArray[index];
|
||||
retVals.cert = cert;
|
||||
retVals.rememberDecision = rememberBox.checked;
|
||||
}
|
||||
|
||||
function doCancel() {
|
||||
let retVals = window.arguments[5].QueryInterface(Ci.nsIWritablePropertyBag2);
|
||||
retVals.setPropertyAsBool("certChosen", false);
|
||||
retVals.setPropertyAsBool("rememberSelection", rememberBox.checked);
|
||||
let { retVals } = window.arguments[0];
|
||||
retVals.cert = null;
|
||||
retVals.rememberDecision = rememberBox.checked;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://pippki/content/clientauthask.css" type="text/css"?>
|
||||
|
||||
<!DOCTYPE window>
|
||||
|
||||
|
@ -13,7 +14,11 @@
|
|||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
onload="onLoad();"
|
||||
>
|
||||
<dialog id="certAuthAsk" buttons="accept,cancel">
|
||||
<dialog
|
||||
id="certAuthAsk"
|
||||
buttons="accept,cancel"
|
||||
buttonidcancel="client-auth-send-no-certificate"
|
||||
>
|
||||
<linkset>
|
||||
<html:link rel="localization" href="security/pippki/pippki.ftl" />
|
||||
</linkset>
|
||||
|
@ -31,25 +36,78 @@
|
|||
<script src="chrome://global/content/editMenuOverlay.js" />
|
||||
|
||||
<description
|
||||
style="font-weight: bold"
|
||||
data-l10n-id="client-auth-site-description"
|
||||
class="important"
|
||||
id="clientAuthSiteIdentification"
|
||||
data-l10n-id="client-auth-site-identification"
|
||||
data-l10n-args='{"hostname":""}'
|
||||
></description>
|
||||
<description id="hostname" />
|
||||
<description id="organization" />
|
||||
<description id="issuer" />
|
||||
|
||||
<description
|
||||
style="font-weight: bold"
|
||||
data-l10n-id="client-auth-choose-cert"
|
||||
></description>
|
||||
<!-- The items in this menulist must never be sorted,
|
||||
but remain in the order filled by the application
|
||||
-->
|
||||
<menulist id="nicknames" oncommand="onCertSelected();" native="true">
|
||||
<menupopup />
|
||||
</menulist>
|
||||
<description data-l10n-id="client-auth-cert-details"></description>
|
||||
<html:textarea readonly="readonly" id="details" style="height: 11em" />
|
||||
<checkbox id="rememberBox" checked="true" native="true" />
|
||||
|
||||
<description
|
||||
class="important"
|
||||
data-l10n-id="client-auth-cert-details"
|
||||
></description>
|
||||
|
||||
<description
|
||||
id="clientAuthCertDetailsIssuedTo"
|
||||
class="details"
|
||||
data-l10n-id="client-auth-cert-details-issued-to"
|
||||
data-l10n-args='{"issuedTo":""}'
|
||||
></description>
|
||||
|
||||
<description
|
||||
id="clientAuthCertDetailsSerialNumber"
|
||||
class="details"
|
||||
data-l10n-id="client-auth-cert-details-serial-number"
|
||||
data-l10n-args='{"serialNumber":""}'
|
||||
></description>
|
||||
|
||||
<description
|
||||
id="clientAuthCertDetailsValidityPeriod"
|
||||
class="details"
|
||||
data-l10n-id="client-auth-cert-details-validity-period"
|
||||
data-l10n-args='{"notBefore":"","notAfter":""}'
|
||||
></description>
|
||||
|
||||
<description
|
||||
id="clientAuthCertDetailsKeyUsages"
|
||||
class="details"
|
||||
data-l10n-id="client-auth-cert-details-key-usages"
|
||||
data-l10n-args='{"keyUsages":""}'
|
||||
></description>
|
||||
|
||||
<description
|
||||
id="clientAuthCertDetailsEmailAddresses"
|
||||
class="details"
|
||||
data-l10n-id="client-auth-cert-details-email-addresses"
|
||||
data-l10n-args='{"emailAddresses":""}'
|
||||
></description>
|
||||
|
||||
<description
|
||||
id="clientAuthCertDetailsIssuedBy"
|
||||
class="details"
|
||||
data-l10n-id="client-auth-cert-details-issued-by"
|
||||
data-l10n-args='{"issuedBy":""}'
|
||||
></description>
|
||||
|
||||
<description
|
||||
id="clientAuthCertDetailsStoredOn"
|
||||
class="details"
|
||||
data-l10n-id="client-auth-cert-details-stored-on"
|
||||
data-l10n-args='{"storedOn":""}'
|
||||
></description>
|
||||
|
||||
<checkbox
|
||||
id="rememberBox"
|
||||
data-l10n-id="client-auth-cert-remember-box"
|
||||
checked="true"
|
||||
native="true"
|
||||
/>
|
||||
</dialog>
|
||||
</window>
|
||||
|
|
|
@ -9,6 +9,7 @@ pippki.jar:
|
|||
content/pippki/certManager.xhtml (content/certManager.xhtml)
|
||||
content/pippki/changepassword.js (content/changepassword.js)
|
||||
content/pippki/changepassword.xhtml (content/changepassword.xhtml)
|
||||
content/pippki/clientauthask.css (content/clientauthask.css)
|
||||
content/pippki/clientauthask.js (content/clientauthask.js)
|
||||
content/pippki/clientauthask.xhtml (content/clientauthask.xhtml)
|
||||
content/pippki/deletecert.js (content/deletecert.js)
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/* 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/. */
|
||||
|
||||
// ClientAuthDialogService implements nsIClientAuthDialogService, and aims to
|
||||
// open a dialog asking the user to select a client authentication certificate.
|
||||
// Ideally the dialog will be tab-modal to the tab corresponding to the load
|
||||
// that resulted in the request for the client authentication certificate.
|
||||
export function ClientAuthDialogService() {}
|
||||
|
||||
// Given a loadContext (CanonicalBrowsingContext), attempts to return a
|
||||
// TabDialogBox for the browser corresponding to loadContext.
|
||||
function getTabDialogBoxForLoadContext(loadContext) {
|
||||
let tabBrowser = loadContext?.topFrameElement?.getTabBrowser();
|
||||
if (!tabBrowser) {
|
||||
return null;
|
||||
}
|
||||
for (let browser of tabBrowser.browsers) {
|
||||
if (browser.browserId == loadContext.top?.browserId) {
|
||||
return tabBrowser.getTabDialogBox(browser);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
ClientAuthDialogService.prototype = {
|
||||
classID: Components.ID("{d7d2490d-2640-411b-9f09-a538803c11ee}"),
|
||||
QueryInterface: ChromeUtils.generateQI(["nsIClientAuthDialogService"]),
|
||||
|
||||
chooseCertificate: function ClientAuthDialogService_chooseCertificate(
|
||||
hostname,
|
||||
certArray,
|
||||
loadContext,
|
||||
callback
|
||||
) {
|
||||
const clientAuthAskURI = "chrome://pippki/content/clientauthask.xhtml";
|
||||
let retVals = { cert: null, rememberDecision: false };
|
||||
// First attempt to find a TabDialogBox for the loadContext. This allows
|
||||
// for a tab-modal dialog specific to the tab causing the load, which is a
|
||||
// better user experience.
|
||||
let tabDialogBox = getTabDialogBoxForLoadContext(loadContext);
|
||||
if (tabDialogBox) {
|
||||
tabDialogBox
|
||||
.open(clientAuthAskURI, {}, { hostname, certArray, retVals })
|
||||
.closedPromise.then(() => {
|
||||
callback.certificateChosen(retVals.cert, retVals.rememberDecision);
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Otherwise, attempt to open a window-modal dialog on the window that at
|
||||
// least has the tab the load is occurring in.
|
||||
let browserWindow = loadContext?.topFrameElement?.ownerGlobal;
|
||||
// Failing that, open a window-modal dialog on the most recent window.
|
||||
if (!browserWindow) {
|
||||
browserWindow = Services.wm.getMostRecentBrowserWindow();
|
||||
}
|
||||
if (browserWindow) {
|
||||
browserWindow.gDialogBox
|
||||
.open(clientAuthAskURI, { hostname, certArray, retVals })
|
||||
.then(() => {
|
||||
callback.certificateChosen(retVals.cert, retVals.rememberDecision);
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Otherwise, continue the connection with no certificate.
|
||||
callback.certificateChosen(null, false);
|
||||
},
|
||||
};
|
|
@ -523,3 +523,11 @@ CommonSocketControl::AsyncGetSecurityInfo(JSContext* aCx,
|
|||
}
|
||||
|
||||
NS_IMETHODIMP CommonSocketControl::Claim() { return NS_ERROR_NOT_IMPLEMENTED; }
|
||||
|
||||
NS_IMETHODIMP CommonSocketControl::SetBrowserId(uint64_t) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP CommonSocketControl::GetBrowserId(uint64_t*) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
|
|
@ -47,7 +47,8 @@ NSSSocketControl::NSSSocketControl(const nsCString& aHostName, int32_t aPort,
|
|||
mSocketCreationTimestamp(TimeStamp::Now()),
|
||||
mPlaintextBytesRead(0),
|
||||
mClaimed(!(providerFlags & nsISocketProvider::IS_SPECULATIVE_CONNECTION)),
|
||||
mPendingSelectClientAuthCertificate(nullptr) {}
|
||||
mPendingSelectClientAuthCertificate(nullptr),
|
||||
mBrowserId(0) {}
|
||||
|
||||
NS_IMETHODIMP
|
||||
NSSSocketControl::GetKEAUsed(int16_t* aKea) {
|
||||
|
@ -698,3 +699,18 @@ NS_IMETHODIMP NSSSocketControl::Claim() {
|
|||
mClaimed = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP NSSSocketControl::SetBrowserId(uint64_t browserId) {
|
||||
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
|
||||
mBrowserId = browserId;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP NSSSocketControl::GetBrowserId(uint64_t* browserId) {
|
||||
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
|
||||
if (!browserId) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
*browserId = mBrowserId;
|
||||
return NS_OK;
|
||||
}
|
||||
|
|
|
@ -71,6 +71,8 @@ class NSSSocketControl final : public CommonSocketControl {
|
|||
NS_IMETHOD SetHandshakeCallbackListener(
|
||||
nsITlsHandshakeCallbackListener* callback) override;
|
||||
NS_IMETHOD Claim() override;
|
||||
NS_IMETHOD SetBrowserId(uint64_t browserId) override;
|
||||
NS_IMETHOD GetBrowserId(uint64_t* browserId) override;
|
||||
|
||||
PRStatus CloseSocketAndDestroy();
|
||||
|
||||
|
@ -335,6 +337,8 @@ class NSSSocketControl final : public CommonSocketControl {
|
|||
RefPtr<mozilla::psm::SharedSSLState> mOwningSharedRef;
|
||||
|
||||
nsCOMPtr<nsITlsHandshakeCallbackListener> mTlsHandshakeCallback;
|
||||
|
||||
uint64_t mBrowserId;
|
||||
};
|
||||
|
||||
#endif // NSSSocketControl_h
|
||||
|
|
|
@ -35,7 +35,8 @@ class SelectTLSClientAuthCertParent : public PSelectTLSClientAuthCertParent {
|
|||
const uint32_t& aProviderFlags,
|
||||
const uint32_t& aProviderTlsFlags,
|
||||
const ByteArray& aServerCertBytes,
|
||||
nsTArray<ByteArray>&& aCANames);
|
||||
nsTArray<ByteArray>&& aCANames,
|
||||
const uint64_t& aBrowsingContextID);
|
||||
|
||||
void TLSClientAuthCertSelected(
|
||||
const nsTArray<uint8_t>& aSelectedCertBytes,
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "TLSClientAuthCertSelection.h"
|
||||
#include "cert_storage/src/cert_storage.h"
|
||||
#include "mozilla/Logging.h"
|
||||
#include "mozilla/dom/BrowsingContext.h"
|
||||
#include "mozilla/ipc/BackgroundChild.h"
|
||||
#include "mozilla/ipc/BackgroundParent.h"
|
||||
#include "mozilla/ipc/PBackgroundChild.h"
|
||||
|
@ -35,7 +36,7 @@
|
|||
#include "nsArray.h"
|
||||
#include "nsArrayUtils.h"
|
||||
#include "nsNSSComponent.h"
|
||||
#include "nsIClientAuthDialogs.h"
|
||||
#include "nsIClientAuthDialogService.h"
|
||||
#include "nsIMutableArray.h"
|
||||
#include "nsINSSComponent.h"
|
||||
#include "NSSCertDBTrustDomain.h"
|
||||
|
@ -407,28 +408,20 @@ ClientAuthCertificateSelected::Run() {
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
SelectClientAuthCertificate::Run() {
|
||||
DoSelectClientAuthCertificate();
|
||||
if (mSelectedCertBytes.Length() > 0) {
|
||||
nsTArray<nsTArray<uint8_t>> selectedCertChainBytes;
|
||||
if (BuildChainForCertificate(mSelectedCertBytes, selectedCertChainBytes) !=
|
||||
pkix::Success) {
|
||||
selectedCertChainBytes.Clear();
|
||||
}
|
||||
mContinuation->SetSelectedClientAuthData(std::move(mSelectedCertBytes),
|
||||
std::move(selectedCertChainBytes));
|
||||
void SelectClientAuthCertificate::DispatchContinuation(
|
||||
nsTArray<uint8_t>&& selectedCertBytes) {
|
||||
nsTArray<nsTArray<uint8_t>> selectedCertChainBytes;
|
||||
if (BuildChainForCertificate(selectedCertBytes, selectedCertChainBytes) !=
|
||||
pkix::Success) {
|
||||
selectedCertChainBytes.Clear();
|
||||
}
|
||||
nsCOMPtr<nsIEventTarget> socketThread =
|
||||
do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
|
||||
if (NS_WARN_IF(!socketThread)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
mContinuation->SetSelectedClientAuthData(std::move(selectedCertBytes),
|
||||
std::move(selectedCertChainBytes));
|
||||
nsCOMPtr<nsIEventTarget> socketThread(
|
||||
do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID));
|
||||
if (socketThread) {
|
||||
(void)socketThread->Dispatch(mContinuation, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
nsresult rv = socketThread->Dispatch(mContinuation, NS_DISPATCH_NORMAL);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Helper function to build a certificate chain from the given certificate to a
|
||||
|
@ -474,32 +467,88 @@ mozilla::pkix::Result SelectClientAuthCertificate::BuildChainForCertificate(
|
|||
return mozilla::pkix::Result::ERROR_UNKNOWN_ISSUER;
|
||||
}
|
||||
|
||||
void SelectClientAuthCertificate::DoSelectClientAuthCertificate() {
|
||||
class ClientAuthDialogCallback : public nsIClientAuthDialogCallback {
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSICLIENTAUTHDIALOGCALLBACK
|
||||
|
||||
ClientAuthDialogCallback(
|
||||
SelectClientAuthCertificate* selectClientAuthCertificate,
|
||||
nsIClientAuthRememberService* clientAuthRememberService)
|
||||
: mSelectClientAuthCertificate(selectClientAuthCertificate),
|
||||
mClientAuthRememberService(clientAuthRememberService) {}
|
||||
|
||||
private:
|
||||
virtual ~ClientAuthDialogCallback() = default;
|
||||
|
||||
RefPtr<SelectClientAuthCertificate> mSelectClientAuthCertificate;
|
||||
nsCOMPtr<nsIClientAuthRememberService> mClientAuthRememberService;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(ClientAuthDialogCallback, nsIClientAuthDialogCallback)
|
||||
|
||||
NS_IMETHODIMP
|
||||
ClientAuthDialogCallback::CertificateChosen(nsIX509Cert* cert,
|
||||
bool rememberDecision) {
|
||||
MOZ_ASSERT(mSelectClientAuthCertificate);
|
||||
if (!mSelectClientAuthCertificate) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
if (mClientAuthRememberService && rememberDecision) {
|
||||
const ClientAuthInfo& info = mSelectClientAuthCertificate->Info();
|
||||
(void)mClientAuthRememberService->RememberDecision(
|
||||
info.HostName(), info.OriginAttributesRef(), cert);
|
||||
}
|
||||
nsTArray<uint8_t> selectedCertBytes;
|
||||
if (cert) {
|
||||
nsresult rv = cert->GetRawDER(selectedCertBytes);
|
||||
if (NS_FAILED(rv)) {
|
||||
selectedCertBytes.Clear();
|
||||
mSelectClientAuthCertificate->DispatchContinuation(
|
||||
std::move(selectedCertBytes));
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
mSelectClientAuthCertificate->DispatchContinuation(
|
||||
std::move(selectedCertBytes));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
SelectClientAuthCertificate::Run() {
|
||||
// We check the value of a pref, so this should only be run on the main
|
||||
// thread.
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsTArray<uint8_t> selectedCertBytes;
|
||||
|
||||
nsCOMPtr<nsINSSComponent> component(do_GetService(PSM_COMPONENT_CONTRACTID));
|
||||
if (NS_WARN_IF(!component)) {
|
||||
return;
|
||||
if (!component) {
|
||||
DispatchContinuation(std::move(selectedCertBytes));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
nsresult rv = component->GetEnterpriseIntermediates(mEnterpriseCertificates);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return;
|
||||
if (NS_FAILED(rv)) {
|
||||
DispatchContinuation(std::move(selectedCertBytes));
|
||||
return rv;
|
||||
}
|
||||
nsTArray<nsTArray<uint8_t>> enterpriseRoots;
|
||||
rv = component->GetEnterpriseRoots(enterpriseRoots);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return;
|
||||
if (NS_FAILED(rv)) {
|
||||
DispatchContinuation(std::move(selectedCertBytes));
|
||||
return rv;
|
||||
}
|
||||
mEnterpriseCertificates.AppendElements(std::move(enterpriseRoots));
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(CheckForSmartCardChanges()))) {
|
||||
return;
|
||||
rv = CheckForSmartCardChanges();
|
||||
if (NS_FAILED(rv)) {
|
||||
DispatchContinuation(std::move(selectedCertBytes));
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (!mPotentialClientCertificates) {
|
||||
return;
|
||||
DispatchContinuation(std::move(selectedCertBytes));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
CERTCertListNode* n = CERT_LIST_HEAD(mPotentialClientCertificates);
|
||||
|
@ -525,7 +574,8 @@ void SelectClientAuthCertificate::DoSelectClientAuthCertificate() {
|
|||
if (CERT_LIST_EMPTY(mPotentialClientCertificates)) {
|
||||
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
||||
("no client certificates available after filtering by CA"));
|
||||
return;
|
||||
DispatchContinuation(std::move(selectedCertBytes));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// find valid user cert and key pair
|
||||
|
@ -545,9 +595,10 @@ void SelectClientAuthCertificate::DoSelectClientAuthCertificate() {
|
|||
}
|
||||
} else {
|
||||
// this is a good cert to present
|
||||
mSelectedCertBytes.AppendElements(node->cert->derCert.data,
|
||||
node->cert->derCert.len);
|
||||
return;
|
||||
selectedCertBytes.AppendElements(node->cert->derCert.data,
|
||||
node->cert->derCert.len);
|
||||
DispatchContinuation(std::move(selectedCertBytes));
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
if (PR_GetError() == SEC_ERROR_BAD_PASSWORD) {
|
||||
|
@ -557,10 +608,11 @@ void SelectClientAuthCertificate::DoSelectClientAuthCertificate() {
|
|||
}
|
||||
|
||||
if (lowPrioNonrepCert) {
|
||||
mSelectedCertBytes.AppendElements(lowPrioNonrepCert->derCert.data,
|
||||
lowPrioNonrepCert->derCert.len);
|
||||
selectedCertBytes.AppendElements(lowPrioNonrepCert->derCert.data,
|
||||
lowPrioNonrepCert->derCert.len);
|
||||
}
|
||||
return;
|
||||
DispatchContinuation(std::move(selectedCertBytes));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Not Auto => ask
|
||||
|
@ -575,95 +627,72 @@ void SelectClientAuthCertificate::DoSelectClientAuthCertificate() {
|
|||
if (cars) {
|
||||
nsCString rememberedDBKey;
|
||||
bool found;
|
||||
nsresult rv = cars->HasRememberedDecision(
|
||||
hostname, mInfo.OriginAttributesRef(), rememberedDBKey, &found);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return;
|
||||
rv = cars->HasRememberedDecision(hostname, mInfo.OriginAttributesRef(),
|
||||
rememberedDBKey, &found);
|
||||
if (NS_FAILED(rv)) {
|
||||
DispatchContinuation(std::move(selectedCertBytes));
|
||||
return rv;
|
||||
}
|
||||
if (found) {
|
||||
// An empty dbKey indicates that the user chose not to use a certificate
|
||||
// and chose to remember this decision
|
||||
if (rememberedDBKey.IsEmpty()) {
|
||||
return;
|
||||
DispatchContinuation(std::move(selectedCertBytes));
|
||||
return NS_OK;
|
||||
}
|
||||
nsCOMPtr<nsIX509CertDB> certdb = do_GetService(NS_X509CERTDB_CONTRACTID);
|
||||
if (NS_WARN_IF(!certdb)) {
|
||||
return;
|
||||
if (!certdb) {
|
||||
DispatchContinuation(std::move(selectedCertBytes));
|
||||
return NS_OK;
|
||||
}
|
||||
nsCOMPtr<nsIX509Cert> foundCert;
|
||||
nsresult rv =
|
||||
certdb->FindCertByDBKey(rememberedDBKey, getter_AddRefs(foundCert));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return;
|
||||
rv = certdb->FindCertByDBKey(rememberedDBKey, getter_AddRefs(foundCert));
|
||||
if (NS_FAILED(rv)) {
|
||||
DispatchContinuation(std::move(selectedCertBytes));
|
||||
return rv;
|
||||
}
|
||||
if (foundCert) {
|
||||
Unused << NS_WARN_IF(
|
||||
NS_FAILED(foundCert->GetRawDER(mSelectedCertBytes)));
|
||||
return;
|
||||
rv = foundCert->GetRawDER(selectedCertBytes);
|
||||
if (NS_FAILED(rv)) {
|
||||
selectedCertBytes.Clear();
|
||||
DispatchContinuation(std::move(selectedCertBytes));
|
||||
return rv;
|
||||
}
|
||||
DispatchContinuation(std::move(selectedCertBytes));
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ask the user to select a certificate
|
||||
nsCOMPtr<nsIClientAuthDialogs> dialogs;
|
||||
UniquePORTString corg(CERT_GetOrgName(&mServerCert->subject));
|
||||
nsAutoCString org(corg.get());
|
||||
|
||||
UniquePORTString cissuer(CERT_GetOrgName(&mServerCert->issuer));
|
||||
nsAutoCString issuer(cissuer.get());
|
||||
|
||||
nsCOMPtr<nsIMutableArray> certArray = nsArrayBase::Create();
|
||||
if (NS_WARN_IF(!certArray)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsTArray<RefPtr<nsIX509Cert>> certArray;
|
||||
for (CERTCertListNode* node = CERT_LIST_HEAD(mPotentialClientCertificates);
|
||||
!CERT_LIST_END(node, mPotentialClientCertificates);
|
||||
node = CERT_LIST_NEXT(node)) {
|
||||
nsCOMPtr<nsIX509Cert> tempCert = new nsNSSCertificate(node->cert);
|
||||
nsresult rv = certArray->AppendElement(tempCert);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return;
|
||||
}
|
||||
RefPtr<nsIX509Cert> tempCert(new nsNSSCertificate(node->cert));
|
||||
certArray.AppendElement(tempCert);
|
||||
}
|
||||
|
||||
// Throw up the client auth dialog and get back the index of the selected
|
||||
// cert
|
||||
rv = getNSSDialogs(getter_AddRefs(dialogs), NS_GET_IID(nsIClientAuthDialogs),
|
||||
NS_CLIENTAUTHDIALOGS_CONTRACTID);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return;
|
||||
nsCOMPtr<nsIClientAuthDialogService> clientAuthDialogService(
|
||||
do_GetService(NS_CLIENTAUTHDIALOGSERVICE_CONTRACTID));
|
||||
if (!clientAuthDialogService) {
|
||||
DispatchContinuation(std::move(selectedCertBytes));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
uint32_t selectedIndex = 0;
|
||||
bool certChosen = false;
|
||||
|
||||
// even if the user has canceled, we want to remember that, to avoid
|
||||
// repeating prompts
|
||||
bool wantRemember = false;
|
||||
rv =
|
||||
dialogs->ChooseCertificate(hostname, mInfo.Port(), org, issuer, certArray,
|
||||
&selectedIndex, &wantRemember, &certChosen);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return;
|
||||
nsCOMPtr<nsILoadContext> loadContext = nullptr;
|
||||
if (mBrowserId != 0) {
|
||||
loadContext =
|
||||
mozilla::dom::BrowsingContext::GetCurrentTopByBrowserId(mBrowserId);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIX509Cert> selectedCert;
|
||||
if (certChosen) {
|
||||
selectedCert = do_QueryElementAt(certArray, selectedIndex);
|
||||
if (NS_WARN_IF(!selectedCert)) {
|
||||
return;
|
||||
}
|
||||
if (NS_WARN_IF(NS_FAILED(selectedCert->GetRawDER(mSelectedCertBytes)))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (cars && wantRemember) {
|
||||
rv = cars->RememberDecision(hostname, mInfo.OriginAttributesRef(),
|
||||
selectedCert);
|
||||
Unused << NS_WARN_IF(NS_FAILED(rv));
|
||||
RefPtr<nsIClientAuthDialogCallback> callback(
|
||||
new ClientAuthDialogCallback(this, cars));
|
||||
rv = clientAuthDialogService->ChooseCertificate(hostname, certArray,
|
||||
loadContext, callback);
|
||||
if (NS_FAILED(rv)) {
|
||||
DispatchContinuation(std::move(selectedCertBytes));
|
||||
return rv;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
SECStatus SSLGetClientAuthDataHook(void* arg, PRFileDesc* socket,
|
||||
|
@ -707,6 +736,12 @@ SECStatus SSLGetClientAuthDataHook(void* arg, PRFileDesc* socket,
|
|||
return SECFailure;
|
||||
}
|
||||
|
||||
uint64_t browserId;
|
||||
if (NS_FAILED(info->GetBrowserId(&browserId))) {
|
||||
PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
|
||||
return SECFailure;
|
||||
}
|
||||
|
||||
nsTArray<nsTArray<uint8_t>> caNames(CollectCANames(caNamesDecoded));
|
||||
|
||||
// Currently, the IPC client certs module only refreshes its view of
|
||||
|
@ -750,7 +785,7 @@ SECStatus SSLGetClientAuthDataHook(void* arg, PRFileDesc* socket,
|
|||
hostname(std::move(hostname)),
|
||||
originAttributes(std::move(originAttributes)), port, providerFlags,
|
||||
providerTlsFlags, serverCertBytes(std::move(serverCertBytes)),
|
||||
caNamesBytes(std::move(caNamesBytes))]() {
|
||||
caNamesBytes(std::move(caNamesBytes)), browserId(browserId)]() {
|
||||
mozilla::ipc::PBackgroundChild* actorChild =
|
||||
mozilla::ipc::BackgroundChild::
|
||||
GetOrCreateForSocketParentBridgeForCurrentThread();
|
||||
|
@ -758,7 +793,7 @@ SECStatus SSLGetClientAuthDataHook(void* arg, PRFileDesc* socket,
|
|||
Unused << actorChild->SendPSelectTLSClientAuthCertConstructor(
|
||||
selectClientAuthCertificate, hostname, originAttributes,
|
||||
port, providerFlags, providerTlsFlags,
|
||||
ByteArray(serverCertBytes), caNamesBytes);
|
||||
ByteArray(serverCertBytes), caNamesBytes, browserId);
|
||||
}
|
||||
}));
|
||||
info->SetPendingSelectClientAuthCertificate(
|
||||
|
@ -770,7 +805,7 @@ SECStatus SSLGetClientAuthDataHook(void* arg, PRFileDesc* socket,
|
|||
nsCOMPtr<nsIRunnable> selectClientAuthCertificate(
|
||||
new SelectClientAuthCertificate(
|
||||
std::move(authInfo), std::move(serverCert), std::move(caNames),
|
||||
std::move(potentialClientCertificates), continuation));
|
||||
std::move(potentialClientCertificates), continuation, browserId));
|
||||
info->SetPendingSelectClientAuthCertificate(
|
||||
std::move(selectClientAuthCertificate));
|
||||
}
|
||||
|
@ -829,7 +864,7 @@ bool SelectTLSClientAuthCertParent::Dispatch(
|
|||
const nsACString& aHostName, const OriginAttributes& aOriginAttributes,
|
||||
const int32_t& aPort, const uint32_t& aProviderFlags,
|
||||
const uint32_t& aProviderTlsFlags, const ByteArray& aServerCertBytes,
|
||||
nsTArray<ByteArray>&& aCANames) {
|
||||
nsTArray<ByteArray>&& aCANames, const uint64_t& aBrowserId) {
|
||||
RefPtr<ClientAuthCertificateSelectedBase> continuation(
|
||||
new RemoteClientAuthCertificateSelected(this));
|
||||
ClientAuthInfo authInfo(aHostName, aOriginAttributes, aPort, aProviderFlags,
|
||||
|
@ -844,8 +879,8 @@ bool SelectTLSClientAuthCertParent::Dispatch(
|
|||
nsresult rv = socketThread->Dispatch(NS_NewRunnableFunction(
|
||||
"SelectTLSClientAuthCertParent::Dispatch",
|
||||
[authInfo(std::move(authInfo)), continuation(std::move(continuation)),
|
||||
serverCertBytes(aServerCertBytes),
|
||||
caNames(std::move(aCANames))]() mutable {
|
||||
serverCertBytes(aServerCertBytes), caNames(std::move(aCANames)),
|
||||
browserId(aBrowserId)]() mutable {
|
||||
SECItem serverCertItem{
|
||||
siBuffer,
|
||||
const_cast<uint8_t*>(serverCertBytes.data().Elements()),
|
||||
|
@ -866,7 +901,7 @@ bool SelectTLSClientAuthCertParent::Dispatch(
|
|||
new SelectClientAuthCertificate(
|
||||
std::move(authInfo), std::move(serverCert),
|
||||
std::move(caNamesArray), std::move(potentialClientCertificates),
|
||||
continuation));
|
||||
continuation, browserId));
|
||||
Unused << NS_DispatchToMainThread(selectClientAuthCertificate);
|
||||
}));
|
||||
return NS_SUCCEEDED(rv);
|
||||
|
|
|
@ -91,21 +91,24 @@ class SelectClientAuthCertificate : public mozilla::Runnable {
|
|||
ClientAuthInfo&& info, mozilla::UniqueCERTCertificate&& serverCert,
|
||||
nsTArray<nsTArray<uint8_t>>&& caNames,
|
||||
mozilla::UniqueCERTCertList&& potentialClientCertificates,
|
||||
ClientAuthCertificateSelectedBase* continuation)
|
||||
ClientAuthCertificateSelectedBase* continuation, uint64_t browserId)
|
||||
: Runnable("SelectClientAuthCertificate"),
|
||||
mInfo(std::move(info)),
|
||||
mServerCert(std::move(serverCert)),
|
||||
mCANames(std::move(caNames)),
|
||||
mPotentialClientCertificates(std::move(potentialClientCertificates)),
|
||||
mContinuation(continuation) {}
|
||||
mContinuation(continuation),
|
||||
mBrowserId(browserId) {}
|
||||
|
||||
NS_IMETHOD Run() override;
|
||||
|
||||
const ClientAuthInfo& Info() { return mInfo; }
|
||||
void DispatchContinuation(nsTArray<uint8_t>&& selectedCertBytes);
|
||||
|
||||
private:
|
||||
mozilla::pkix::Result BuildChainForCertificate(
|
||||
nsTArray<uint8_t>& certBytes,
|
||||
nsTArray<nsTArray<uint8_t>>& certChainBytes);
|
||||
void DoSelectClientAuthCertificate();
|
||||
|
||||
ClientAuthInfo mInfo;
|
||||
mozilla::UniqueCERTCertificate mServerCert;
|
||||
|
@ -114,7 +117,9 @@ class SelectClientAuthCertificate : public mozilla::Runnable {
|
|||
RefPtr<ClientAuthCertificateSelectedBase> mContinuation;
|
||||
|
||||
nsTArray<nsTArray<uint8_t>> mEnterpriseCertificates;
|
||||
nsTArray<uint8_t> mSelectedCertBytes;
|
||||
|
||||
uint64_t mBrowserId;
|
||||
nsCOMPtr<nsIInterfaceRequestor> mSecurityCallbacks;
|
||||
};
|
||||
|
||||
#endif // SECURITY_MANAGER_SSL_TLSCLIENTAUTHCERTSELECTION_H_
|
||||
|
|
|
@ -146,4 +146,10 @@ Classes = [
|
|||
'type': 'mozilla::DataStorageManager',
|
||||
'headers': ['/security/manager/ssl//DataStorage.h'],
|
||||
},
|
||||
{
|
||||
'cid': '{d7d2490d-2640-411b-9f09-a538803c11ee}',
|
||||
'contract_ids': ['@mozilla.org/security/ClientAuthDialogService;1'],
|
||||
'esModule': 'resource://gre/modules/psm/ClientAuthDialogService.sys.mjs',
|
||||
'constructor': 'ClientAuthDialogService',
|
||||
},
|
||||
]
|
||||
|
|
|
@ -22,7 +22,7 @@ XPIDL_SOURCES += [
|
|||
"nsICertOverrideService.idl",
|
||||
"nsICertStorage.idl",
|
||||
"nsICertTree.idl",
|
||||
"nsIClientAuthDialogs.idl",
|
||||
"nsIClientAuthDialogService.idl",
|
||||
"nsIClientAuthRememberService.idl",
|
||||
"nsIContentSignatureVerifier.idl",
|
||||
"nsICryptoHash.idl",
|
||||
|
@ -56,6 +56,7 @@ XPCOM_MANIFESTS += [
|
|||
]
|
||||
|
||||
EXTRA_JS_MODULES.psm += [
|
||||
"ClientAuthDialogService.sys.mjs",
|
||||
"DER.sys.mjs",
|
||||
"RemoteSecuritySettings.sys.mjs",
|
||||
"X509.sys.mjs",
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/* 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/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
interface nsILoadContext;
|
||||
interface nsIX509Cert;
|
||||
|
||||
[scriptable, function, uuid(6b00d96d-fb8a-4c9f-9632-c9e1235befce)]
|
||||
interface nsIClientAuthDialogCallback : nsISupports
|
||||
{
|
||||
void certificateChosen(in nsIX509Cert cert, in bool rememberDecision);
|
||||
};
|
||||
|
||||
/**
|
||||
* Provides UI when a server requests a TLS client authentication certificate.
|
||||
*/
|
||||
[scriptable, uuid(fa4c7520-1433-11d5-ba24-00108303b117)]
|
||||
interface nsIClientAuthDialogService : nsISupports
|
||||
{
|
||||
/**
|
||||
* Called when a user is asked to choose a certificate for client auth.
|
||||
*
|
||||
* @param hostname Hostname of the server.
|
||||
* @param certArray Array of certificates the user can choose from.
|
||||
* @param loadContext The nsILoadContext of the connection requesting a
|
||||
* certificate. May be null, in which case the
|
||||
* implementation will use the most recent window to show
|
||||
* UI.
|
||||
* @param callback The nsIClientAuthDialogCallback to call when a certificate
|
||||
* has been chosen (or no certificate).
|
||||
*/
|
||||
[must_use]
|
||||
void chooseCertificate(in AUTF8String hostname,
|
||||
in Array<nsIX509Cert> certArray,
|
||||
in nsILoadContext loadContext,
|
||||
in nsIClientAuthDialogCallback callback);
|
||||
};
|
||||
|
||||
%{C++
|
||||
#define NS_CLIENTAUTHDIALOGSERVICE_CONTRACTID "@mozilla.org/security/ClientAuthDialogService;1"
|
||||
%}
|
|
@ -1,41 +0,0 @@
|
|||
/* 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/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
interface nsIArray;
|
||||
interface nsIInterfaceRequestor;
|
||||
|
||||
/**
|
||||
* Provides UI for SSL client-auth dialogs.
|
||||
*/
|
||||
[scriptable, uuid(fa4c7520-1433-11d5-ba24-00108303b117)]
|
||||
interface nsIClientAuthDialogs : nsISupports
|
||||
{
|
||||
/**
|
||||
* Called when a user is asked to choose a certificate for client auth.
|
||||
*
|
||||
* @param hostname Hostname of the server.
|
||||
* @param port Port of the server.
|
||||
* @param organization Organization field of the server cert.
|
||||
* @param issuerOrg Organization field of the issuer cert of the server cert.
|
||||
* @param certList List of certificates the user can choose from.
|
||||
* @param selectedIndex Index of the cert in |certList| that the user chose.
|
||||
* Ignored if the return value is false.
|
||||
* @param rememberClientAuthCertificate Whether to remember selection.
|
||||
* @return true if a certificate was chosen. false if the user canceled.
|
||||
*/
|
||||
[must_use]
|
||||
boolean chooseCertificate(in AUTF8String hostname,
|
||||
in long port,
|
||||
in AUTF8String organization,
|
||||
in AUTF8String issuerOrg,
|
||||
in nsIArray certList,
|
||||
out unsigned long selectedIndex,
|
||||
out boolean rememberClientAuthCertificate);
|
||||
};
|
||||
|
||||
%{C++
|
||||
#define NS_CLIENTAUTHDIALOGS_CONTRACTID "@mozilla.org/nsClientAuthDialogs;1"
|
||||
%}
|
|
@ -184,4 +184,12 @@ interface nsITLSSocketControl : nsISupports {
|
|||
* Claim a speculative connection.
|
||||
*/
|
||||
void claim();
|
||||
|
||||
/**
|
||||
* The top-level outer content window ID (called "browserId" in networking
|
||||
* code) associated with this connection, if any (otherwise, 0). Useful for
|
||||
* associating this connection with a browser tab in order to show UI (e.g.
|
||||
* the client authentication certificate selection dialog).
|
||||
*/
|
||||
attribute uint64_t browserId;
|
||||
};
|
||||
|
|
|
@ -42,7 +42,6 @@
|
|||
#include "nsCharSeparatedTokenizer.h"
|
||||
#include "nsClientAuthRemember.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsIClientAuthDialogs.h"
|
||||
#include "nsISocketProvider.h"
|
||||
#include "nsISocketTransport.h"
|
||||
#include "nsIWebProgressListener.h"
|
||||
|
|
|
@ -62,7 +62,7 @@ async function testHelper(connectURL, expectedURL) {
|
|||
}
|
||||
|
||||
async function openRequireClientCert() {
|
||||
gClientAuthDialogs.chooseCertificateCalled = false;
|
||||
gClientAuthDialogService.chooseCertificateCalled = false;
|
||||
await testHelper(
|
||||
"https://requireclientcert.example.com:443",
|
||||
"https://requireclientcert.example.com/"
|
||||
|
@ -70,7 +70,7 @@ async function openRequireClientCert() {
|
|||
}
|
||||
|
||||
async function openRequireClientCert2() {
|
||||
gClientAuthDialogs.chooseCertificateCalled = false;
|
||||
gClientAuthDialogService.chooseCertificateCalled = false;
|
||||
await testHelper(
|
||||
"https://requireclientcert-2.example.com:443",
|
||||
"https://requireclientcert-2.example.com/"
|
||||
|
@ -124,7 +124,7 @@ const gClientAuthRememberService = {
|
|||
QueryInterface: ChromeUtils.generateQI(["nsIClientAuthRememberService"]),
|
||||
};
|
||||
|
||||
const gClientAuthDialogs = {
|
||||
const gClientAuthDialogService = {
|
||||
_chooseCertificateCalled: false,
|
||||
|
||||
get chooseCertificateCalled() {
|
||||
|
@ -135,22 +135,12 @@ const gClientAuthDialogs = {
|
|||
this._chooseCertificateCalled = value;
|
||||
},
|
||||
|
||||
chooseCertificate(
|
||||
hostname,
|
||||
port,
|
||||
organization,
|
||||
issuerOrg,
|
||||
certList,
|
||||
selectedIndex,
|
||||
rememberClientAuthCertificate
|
||||
) {
|
||||
rememberClientAuthCertificate.value = true;
|
||||
chooseCertificate(hostname, certArray, loadContext, callback) {
|
||||
this.chooseCertificateCalled = true;
|
||||
selectedIndex.value = 0;
|
||||
return true;
|
||||
callback.certificateChosen(certArray[0], true);
|
||||
},
|
||||
|
||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIClientAuthDialogs]),
|
||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIClientAuthDialogService]),
|
||||
};
|
||||
|
||||
add_task(async function testRememberedDecisionsUI() {
|
||||
|
@ -252,9 +242,9 @@ add_task(async function testRememberedDecisionsUI() {
|
|||
});
|
||||
|
||||
add_task(async function testDeletingRememberedDecisions() {
|
||||
let clientAuthDialogsCID = MockRegistrar.register(
|
||||
"@mozilla.org/nsClientAuthDialogs;1",
|
||||
gClientAuthDialogs
|
||||
let clientAuthDialogServiceCID = MockRegistrar.register(
|
||||
"@mozilla.org/security/ClientAuthDialogService;1",
|
||||
gClientAuthDialogService
|
||||
);
|
||||
let cars = Cc["@mozilla.org/security/clientAuthRememberService;1"].getService(
|
||||
Ci.nsIClientAuthRememberService
|
||||
|
@ -262,19 +252,19 @@ add_task(async function testDeletingRememberedDecisions() {
|
|||
|
||||
await openRequireClientCert();
|
||||
Assert.ok(
|
||||
gClientAuthDialogs.chooseCertificateCalled,
|
||||
gClientAuthDialogService.chooseCertificateCalled,
|
||||
"chooseCertificate should have been called if visiting 'requireclientcert.example.com' for the first time"
|
||||
);
|
||||
|
||||
await openRequireClientCert();
|
||||
Assert.ok(
|
||||
!gClientAuthDialogs.chooseCertificateCalled,
|
||||
!gClientAuthDialogService.chooseCertificateCalled,
|
||||
"chooseCertificate should not have been called if visiting 'requireclientcert.example.com' for the second time"
|
||||
);
|
||||
|
||||
await openRequireClientCert2();
|
||||
Assert.ok(
|
||||
gClientAuthDialogs.chooseCertificateCalled,
|
||||
gClientAuthDialogService.chooseCertificateCalled,
|
||||
"chooseCertificate should have been called if visiting 'requireclientcert-2.example.com' for the first time"
|
||||
);
|
||||
|
||||
|
@ -283,15 +273,15 @@ add_task(async function testDeletingRememberedDecisions() {
|
|||
|
||||
await openRequireClientCert();
|
||||
Assert.ok(
|
||||
gClientAuthDialogs.chooseCertificateCalled,
|
||||
gClientAuthDialogService.chooseCertificateCalled,
|
||||
"chooseCertificate should have been called after removing all remembered decisions for 'requireclientcert.example.com'"
|
||||
);
|
||||
|
||||
await openRequireClientCert2();
|
||||
Assert.ok(
|
||||
!gClientAuthDialogs.chooseCertificateCalled,
|
||||
!gClientAuthDialogService.chooseCertificateCalled,
|
||||
"chooseCertificate should not have been called if visiting 'requireclientcert-2.example.com' for the second time"
|
||||
);
|
||||
|
||||
MockRegistrar.unregister(clientAuthDialogsCID);
|
||||
MockRegistrar.unregister(clientAuthDialogServiceCID);
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"use strict";
|
||||
|
||||
// Tests various scenarios connecting to a server that requires client cert
|
||||
// authentication. Also tests that nsIClientAuthDialogs.chooseCertificate
|
||||
// authentication. Also tests that nsIClientAuthDialogService.chooseCertificate
|
||||
// is called at the appropriate times and with the correct arguments.
|
||||
|
||||
const { MockRegistrar } = ChromeUtils.importESModule(
|
||||
|
@ -27,8 +27,8 @@ let cars = Cc["@mozilla.org/security/clientAuthRememberService;1"].getService(
|
|||
|
||||
var gExpectedClientCertificateChoices;
|
||||
|
||||
// Mock implementation of nsIClientAuthDialogs.
|
||||
const gClientAuthDialogs = {
|
||||
// Mock implementation of nsIClientAuthDialogService.
|
||||
const gClientAuthDialogService = {
|
||||
_state: DialogState.ASSERT_NOT_CALLED,
|
||||
_rememberClientAuthCertificate: false,
|
||||
_chooseCertificateCalled: false,
|
||||
|
@ -59,52 +59,30 @@ const gClientAuthDialogs = {
|
|||
this._chooseCertificateCalled = value;
|
||||
},
|
||||
|
||||
chooseCertificate(
|
||||
hostname,
|
||||
port,
|
||||
organization,
|
||||
issuerOrg,
|
||||
certList,
|
||||
selectedIndex,
|
||||
rememberClientAuthCertificate
|
||||
) {
|
||||
chooseCertificate(hostname, certArray, loadContext, callback) {
|
||||
this.chooseCertificateCalled = true;
|
||||
Assert.notEqual(
|
||||
this.state,
|
||||
DialogState.ASSERT_NOT_CALLED,
|
||||
"chooseCertificate() should be called only when expected"
|
||||
);
|
||||
|
||||
rememberClientAuthCertificate.value = this.rememberClientAuthCertificate;
|
||||
|
||||
Assert.equal(
|
||||
hostname,
|
||||
"requireclientcert.example.com",
|
||||
"Hostname should be 'requireclientcert.example.com'"
|
||||
);
|
||||
Assert.equal(port, 443, "Port should be 443");
|
||||
Assert.equal(
|
||||
organization,
|
||||
"",
|
||||
"Server cert Organization should be empty/not present"
|
||||
);
|
||||
Assert.equal(
|
||||
issuerOrg,
|
||||
"Mozilla Testing",
|
||||
"Server cert issuer Organization should be 'Mozilla Testing'"
|
||||
);
|
||||
|
||||
// For mochitests, the cert at build/pgo/certs/mochitest.client should be
|
||||
// selectable as well as one of the PGO certs we loaded in `setup`, so we do
|
||||
// some brief checks to confirm this.
|
||||
Assert.notEqual(certList, null, "Cert list should not be null");
|
||||
Assert.notEqual(certArray, null, "Cert list should not be null");
|
||||
Assert.equal(
|
||||
certList.length,
|
||||
certArray.length,
|
||||
gExpectedClientCertificateChoices,
|
||||
`${gExpectedClientCertificateChoices} certificates should be available`
|
||||
);
|
||||
|
||||
for (let cert of certList.enumerate(Ci.nsIX509Cert)) {
|
||||
for (let cert of certArray) {
|
||||
Assert.notEqual(cert, null, "Cert list should contain nsIX509Certs");
|
||||
Assert.equal(
|
||||
cert.issuerCommonName,
|
||||
|
@ -114,22 +92,25 @@ const gClientAuthDialogs = {
|
|||
}
|
||||
|
||||
if (this.state == DialogState.RETURN_CERT_SELECTED) {
|
||||
selectedIndex.value = 0;
|
||||
return true;
|
||||
callback.certificateChosen(
|
||||
certArray[0],
|
||||
this.rememberClientAuthCertificate
|
||||
);
|
||||
} else {
|
||||
callback.certificateChosen(null, this.rememberClientAuthCertificate);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
QueryInterface: ChromeUtils.generateQI(["nsIClientAuthDialogs"]),
|
||||
QueryInterface: ChromeUtils.generateQI(["nsIClientAuthDialogService"]),
|
||||
};
|
||||
|
||||
add_setup(async function () {
|
||||
let clientAuthDialogsCID = MockRegistrar.register(
|
||||
"@mozilla.org/nsClientAuthDialogs;1",
|
||||
gClientAuthDialogs
|
||||
let clientAuthDialogServiceCID = MockRegistrar.register(
|
||||
"@mozilla.org/security/ClientAuthDialogService;1",
|
||||
gClientAuthDialogService
|
||||
);
|
||||
registerCleanupFunction(() => {
|
||||
MockRegistrar.unregister(clientAuthDialogsCID);
|
||||
MockRegistrar.unregister(clientAuthDialogServiceCID);
|
||||
});
|
||||
|
||||
// This CA has the expected keyCertSign and cRLSign usages. It should not be
|
||||
|
@ -181,7 +162,7 @@ async function testHelper(
|
|||
options = undefined,
|
||||
expectStringInPage = undefined
|
||||
) {
|
||||
gClientAuthDialogs.chooseCertificateCalled = false;
|
||||
gClientAuthDialogService.chooseCertificateCalled = false;
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["security.default_personal_cert", prefValue]],
|
||||
});
|
||||
|
@ -216,7 +197,7 @@ async function testHelper(
|
|||
}
|
||||
|
||||
Assert.equal(
|
||||
gClientAuthDialogs.chooseCertificateCalled,
|
||||
gClientAuthDialogService.chooseCertificateCalled,
|
||||
expectCallingChooseCertificate,
|
||||
"chooseCertificate should have been called if we were expecting it to be called"
|
||||
);
|
||||
|
@ -244,9 +225,9 @@ async function testHelper(
|
|||
}
|
||||
|
||||
// Test that if a certificate is chosen automatically the connection succeeds,
|
||||
// and that nsIClientAuthDialogs.chooseCertificate() is never called.
|
||||
// and that nsIClientAuthDialogService.chooseCertificate() is never called.
|
||||
add_task(async function testCertChosenAutomatically() {
|
||||
gClientAuthDialogs.state = DialogState.ASSERT_NOT_CALLED;
|
||||
gClientAuthDialogService.state = DialogState.ASSERT_NOT_CALLED;
|
||||
await testHelper(
|
||||
"Select Automatically",
|
||||
"https://requireclientcert.example.com/",
|
||||
|
@ -261,7 +242,7 @@ add_task(async function testCertChosenAutomatically() {
|
|||
// Test that if the user doesn't choose a certificate, the connection fails and
|
||||
// an error page is displayed.
|
||||
add_task(async function testCertNotChosenByUser() {
|
||||
gClientAuthDialogs.state = DialogState.RETURN_CERT_NOT_SELECTED;
|
||||
gClientAuthDialogService.state = DialogState.RETURN_CERT_NOT_SELECTED;
|
||||
await testHelper(
|
||||
"Ask Every Time",
|
||||
"https://requireclientcert.example.com/",
|
||||
|
@ -278,7 +259,7 @@ add_task(async function testCertNotChosenByUser() {
|
|||
|
||||
// Test that if the user chooses a certificate the connection suceeeds.
|
||||
add_task(async function testCertChosenByUser() {
|
||||
gClientAuthDialogs.state = DialogState.RETURN_CERT_SELECTED;
|
||||
gClientAuthDialogService.state = DialogState.RETURN_CERT_SELECTED;
|
||||
await testHelper(
|
||||
"Ask Every Time",
|
||||
"https://requireclientcert.example.com/",
|
||||
|
@ -290,8 +271,8 @@ add_task(async function testCertChosenByUser() {
|
|||
|
||||
// Test that the cancel decision is remembered correctly
|
||||
add_task(async function testEmptyCertChosenByUser() {
|
||||
gClientAuthDialogs.state = DialogState.RETURN_CERT_NOT_SELECTED;
|
||||
gClientAuthDialogs.rememberClientAuthCertificate = true;
|
||||
gClientAuthDialogService.state = DialogState.RETURN_CERT_NOT_SELECTED;
|
||||
gClientAuthDialogService.rememberClientAuthCertificate = true;
|
||||
await testHelper(
|
||||
"Ask Every Time",
|
||||
"https://requireclientcert.example.com/",
|
||||
|
@ -316,8 +297,8 @@ add_task(async function testEmptyCertChosenByUser() {
|
|||
// again should be asked to choose a certificate (i.e. private state should not
|
||||
// be remembered/used in non-private contexts).
|
||||
add_task(async function testClearPrivateBrowsingState() {
|
||||
gClientAuthDialogs.rememberClientAuthCertificate = true;
|
||||
gClientAuthDialogs.state = DialogState.RETURN_CERT_SELECTED;
|
||||
gClientAuthDialogService.rememberClientAuthCertificate = true;
|
||||
gClientAuthDialogService.state = DialogState.RETURN_CERT_SELECTED;
|
||||
await testHelper(
|
||||
"Ask Every Time",
|
||||
"https://requireclientcert.example.com/",
|
||||
|
@ -371,7 +352,7 @@ add_task(async function testCertFilteringWithIntermediate() {
|
|||
let nssComponent = Cc["@mozilla.org/psm;1"].getService(Ci.nsINSSComponent);
|
||||
nssComponent.addEnterpriseIntermediate(intermediateBytes);
|
||||
gExpectedClientCertificateChoices = 4;
|
||||
gClientAuthDialogs.state = DialogState.RETURN_CERT_SELECTED;
|
||||
gClientAuthDialogService.state = DialogState.RETURN_CERT_SELECTED;
|
||||
await testHelper(
|
||||
"Ask Every Time",
|
||||
"https://requireclientcert.example.com/",
|
||||
|
@ -386,9 +367,9 @@ add_task(async function testCertFilteringWithIntermediate() {
|
|||
});
|
||||
|
||||
// Test that if the server certificate does not validate successfully,
|
||||
// nsIClientAuthDialogs.chooseCertificate() is never called.
|
||||
// nsIClientAuthDialogService.chooseCertificate() is never called.
|
||||
add_task(async function testNoDialogForUntrustedServerCertificate() {
|
||||
gClientAuthDialogs.state = DialogState.ASSERT_NOT_CALLED;
|
||||
gClientAuthDialogService.state = DialogState.ASSERT_NOT_CALLED;
|
||||
await testHelper(
|
||||
"Ask Every Time",
|
||||
"https://requireclientcert-untrusted.example.com/",
|
||||
|
|
|
@ -24,28 +24,22 @@ const TEST_PATH = getRootDirectory(gTestPath).replace(
|
|||
|
||||
let chooseCertificateCalled = false;
|
||||
|
||||
const clientAuthDialogs = {
|
||||
chooseCertificate(
|
||||
hostname,
|
||||
port,
|
||||
organization,
|
||||
issuerOrg,
|
||||
certList,
|
||||
selectedIndex,
|
||||
rememberClientAuthCertificate
|
||||
) {
|
||||
is(certList.length, 1, "should have only one client certificate available");
|
||||
selectedIndex.value = 0;
|
||||
rememberClientAuthCertificate.value = false;
|
||||
const clientAuthDialogService = {
|
||||
chooseCertificate(hostname, certArray, loadContext, callback) {
|
||||
is(
|
||||
certArray.length,
|
||||
1,
|
||||
"should have only one client certificate available"
|
||||
);
|
||||
ok(
|
||||
!chooseCertificateCalled,
|
||||
"chooseCertificate should only be called once"
|
||||
);
|
||||
chooseCertificateCalled = true;
|
||||
return true;
|
||||
callback.certificateChosen(certArray[0], false);
|
||||
},
|
||||
|
||||
QueryInterface: ChromeUtils.generateQI(["nsIClientAuthDialogs"]),
|
||||
QueryInterface: ChromeUtils.generateQI(["nsIClientAuthDialogService"]),
|
||||
};
|
||||
|
||||
add_setup(async function () {
|
||||
|
@ -57,12 +51,12 @@ add_setup(async function () {
|
|||
["security.default_personal_cert", "Ask Every Time"],
|
||||
],
|
||||
});
|
||||
let clientAuthDialogsCID = MockRegistrar.register(
|
||||
"@mozilla.org/nsClientAuthDialogs;1",
|
||||
clientAuthDialogs
|
||||
let clientAuthDialogServiceCID = MockRegistrar.register(
|
||||
"@mozilla.org/security/ClientAuthDialogService;1",
|
||||
clientAuthDialogService
|
||||
);
|
||||
registerCleanupFunction(async function () {
|
||||
MockRegistrar.unregister(clientAuthDialogsCID);
|
||||
MockRegistrar.unregister(clientAuthDialogServiceCID);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -32,27 +32,17 @@ var cert;
|
|||
* 2. The return value nsIWritablePropertyBag2 passed to the dialog.
|
||||
*/
|
||||
function openClientAuthDialog(cert) {
|
||||
let certList = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
|
||||
certList.appendElement(cert);
|
||||
|
||||
let returnVals = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
|
||||
Ci.nsIWritablePropertyBag2
|
||||
);
|
||||
let certArray = [cert];
|
||||
let retVals = { cert: undefined, rememberDecision: undefined };
|
||||
let win = window.openDialog(
|
||||
"chrome://pippki/content/clientauthask.xhtml",
|
||||
"",
|
||||
"",
|
||||
TEST_HOSTNAME,
|
||||
TEST_ORG,
|
||||
TEST_ISSUER_ORG,
|
||||
TEST_PORT,
|
||||
certList,
|
||||
returnVals
|
||||
{ hostname: TEST_HOSTNAME, certArray, retVals }
|
||||
);
|
||||
return TestUtils.topicObserved("cert-dialog-loaded").then(() => [
|
||||
win,
|
||||
returnVals,
|
||||
]);
|
||||
return TestUtils.topicObserved("cert-dialog-loaded").then(() => {
|
||||
return { win, retVals };
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -65,62 +55,46 @@ function openClientAuthDialog(cert) {
|
|||
* @param {string} notAfter
|
||||
* The formatted notAfter date of mochitest.client.
|
||||
*/
|
||||
function checkDialogContents(win, notBefore, notAfter) {
|
||||
is(
|
||||
win.document.getElementById("hostname").textContent,
|
||||
`${TEST_HOSTNAME}:${TEST_PORT}`,
|
||||
"Actual and expected hostname and port should be equal"
|
||||
async function checkDialogContents(win, notBefore, notAfter) {
|
||||
await TestUtils.waitForCondition(() => {
|
||||
return win.document
|
||||
.getElementById("clientAuthSiteIdentification")
|
||||
.textContent.includes(`${TEST_HOSTNAME}`);
|
||||
});
|
||||
let nicknames = win.document.getElementById("nicknames");
|
||||
await TestUtils.waitForCondition(() => {
|
||||
return nicknames.label == "Mochitest client [03]";
|
||||
});
|
||||
await TestUtils.waitForCondition(() => {
|
||||
return nicknames.itemCount == 1;
|
||||
});
|
||||
let subject = win.document.getElementById("clientAuthCertDetailsIssuedTo");
|
||||
await TestUtils.waitForCondition(() => {
|
||||
return subject.textContent == "Issued to: CN=Mochitest client";
|
||||
});
|
||||
let serialNum = win.document.getElementById(
|
||||
"clientAuthCertDetailsSerialNumber"
|
||||
);
|
||||
is(
|
||||
win.document.getElementById("organization").textContent,
|
||||
`Organization: “${TEST_ORG}”`,
|
||||
"Actual and expected organization should be equal"
|
||||
);
|
||||
is(
|
||||
win.document.getElementById("issuer").textContent,
|
||||
`Issued Under: “${TEST_ISSUER_ORG}”`,
|
||||
"Actual and expected issuer organization should be equal"
|
||||
);
|
||||
|
||||
is(
|
||||
win.document.getElementById("nicknames").label,
|
||||
"Mochitest client [03]",
|
||||
"Actual and expected selected cert nickname and serial should be equal"
|
||||
);
|
||||
is(
|
||||
win.document.getElementById("nicknames").itemCount,
|
||||
1,
|
||||
"correct number of items"
|
||||
);
|
||||
|
||||
let [subject, serialNum, validity, issuer, tokenName] = win.document
|
||||
.getElementById("details")
|
||||
.value.split("\n");
|
||||
is(
|
||||
subject,
|
||||
"Issued to: CN=Mochitest client",
|
||||
"Actual and expected subject should be equal"
|
||||
);
|
||||
is(
|
||||
serialNum,
|
||||
"Serial number: 03",
|
||||
"Actual and expected serial number should be equal"
|
||||
);
|
||||
is(
|
||||
validity,
|
||||
`Valid from ${notBefore} to ${notAfter}`,
|
||||
"Actual and expected validity should be equal"
|
||||
);
|
||||
is(
|
||||
issuer,
|
||||
"Issued by: OU=Profile Guided Optimization,O=Mozilla Testing,CN=Temporary Certificate Authority",
|
||||
"Actual and expected issuer should be equal"
|
||||
);
|
||||
is(
|
||||
tokenName,
|
||||
"Stored on: Software Security Device",
|
||||
"Actual and expected token name should be equal"
|
||||
await TestUtils.waitForCondition(() => {
|
||||
return serialNum.textContent == "Serial number: 03";
|
||||
});
|
||||
let validity = win.document.getElementById(
|
||||
"clientAuthCertDetailsValidityPeriod"
|
||||
);
|
||||
await TestUtils.waitForCondition(() => {
|
||||
return validity.textContent == `Valid from ${notBefore} to ${notAfter}`;
|
||||
});
|
||||
let issuer = win.document.getElementById("clientAuthCertDetailsIssuedBy");
|
||||
await TestUtils.waitForCondition(() => {
|
||||
return (
|
||||
issuer.textContent ==
|
||||
"Issued by: OU=Profile Guided Optimization,O=Mozilla Testing,CN=Temporary Certificate Authority"
|
||||
);
|
||||
});
|
||||
let tokenName = win.document.getElementById("clientAuthCertDetailsStoredOn");
|
||||
await TestUtils.waitForCondition(() => {
|
||||
return tokenName.textContent == "Stored on: Software Security Device";
|
||||
});
|
||||
}
|
||||
|
||||
function findCertByCommonName(commonName) {
|
||||
|
@ -144,8 +118,8 @@ add_task(async function testContents() {
|
|||
dateStyle: "medium",
|
||||
timeStyle: "long",
|
||||
});
|
||||
let [win] = await openClientAuthDialog(cert);
|
||||
checkDialogContents(
|
||||
let { win } = await openClientAuthDialog(cert);
|
||||
await checkDialogContents(
|
||||
win,
|
||||
formatter.format(new Date(cert.validity.notBefore / 1000)),
|
||||
formatter.format(new Date(cert.validity.notAfter / 1000))
|
||||
|
@ -155,41 +129,33 @@ add_task(async function testContents() {
|
|||
|
||||
// Test that the right values are returned when the dialog is accepted.
|
||||
add_task(async function testAcceptDialogReturnValues() {
|
||||
let [win, retVals] = await openClientAuthDialog(cert);
|
||||
let { win, retVals } = await openClientAuthDialog(cert);
|
||||
win.document.getElementById("rememberBox").checked = true;
|
||||
info("Accepting dialog");
|
||||
win.document.getElementById("certAuthAsk").acceptDialog();
|
||||
await BrowserTestUtils.windowClosed(win);
|
||||
|
||||
is(retVals.cert, cert, "cert should be returned as chosen cert");
|
||||
ok(
|
||||
retVals.get("certChosen"),
|
||||
"Return value should signal user chose a certificate"
|
||||
);
|
||||
is(
|
||||
retVals.get("selectedIndex"),
|
||||
0,
|
||||
"0 should be returned as the selected index"
|
||||
);
|
||||
ok(
|
||||
retVals.get("rememberSelection"),
|
||||
retVals.rememberDecision,
|
||||
"Return value should signal 'Remember this decision' checkbox was checked"
|
||||
);
|
||||
});
|
||||
|
||||
// Test that the right values are returned when the dialog is canceled.
|
||||
add_task(async function testCancelDialogReturnValues() {
|
||||
let [win, retVals] = await openClientAuthDialog(cert);
|
||||
let { win, retVals } = await openClientAuthDialog(cert);
|
||||
win.document.getElementById("rememberBox").checked = false;
|
||||
info("Canceling dialog");
|
||||
win.document.getElementById("certAuthAsk").cancelDialog();
|
||||
await BrowserTestUtils.windowClosed(win);
|
||||
|
||||
ok(
|
||||
!retVals.get("certChosen"),
|
||||
!retVals.cert,
|
||||
"Return value should signal user did not choose a certificate"
|
||||
);
|
||||
ok(
|
||||
!retVals.get("rememberSelection"),
|
||||
!retVals.rememberDecision,
|
||||
"Return value should signal 'Remember this decision' checkbox was unchecked"
|
||||
);
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче