Bug 1204301 - In an e10s window, Nightly does not bring up the master password dialog when required for TLS client authentication
TLS authentication. Huh.

This is not a thing a see too often. The only time I've authenticated with a certificate is on the http://startssl.com/ site for managing my certs.


That's down in some old-school code, probably from early on days.

So that XUL dialog is here: https://dxr.mozilla.org/mozilla-central/rev/9ed17db42e3e46f1c712e4dffd62d54e915e0fac/security/manager/pki/resources/content/clientauthask.xul

And its script is here: https://dxr.mozilla.org/mozilla-central/rev/9ed17db42e3e46f1c712e4dffd62d54e915e0fac/security/manager/pki/resources/content/clientauthask.js

And that dialog is opened here: https://dxr.mozilla.org/mozilla-central/rev/9ed17db42e3e46f1c712e4dffd62d54e915e0fac/security/manager/pki/nsNSSDialogs.cpp#225

Hrm, and now I'm down in NSS. Time to be cautious.

Here's the stack for the thing that eventually causes us to open the Master Password prompt in non-e10s mode:

* thread #5: tid = 0x2520cd, 0x0000000103577f7d XUL`nsNSS_SSLGetClientAuthData(void*, PRFileDesc*, CERTDistNamesStr*, CERTCertificateStr**, SECKEYPrivateKeyStr**) [inlined] nsNSSShutDownPreventionLock::nsNSSShutDownPreventionLock() at nsNSSShutDown.cpp:340, name = 'Socket Thread', stop reason = breakpoint 4.1
frame #0: 0x0000000103577f7d XUL`nsNSS_SSLGetClientAuthData(void*, PRFileDesc*, CERTDistNamesStr*, CERTCertificateStr**, SECKEYPrivateKeyStr**) [inlined] nsNSSShutDownPreventionLock::nsNSSShutDownPreventionLock() at nsNSSShutDown.cpp:340
frame #1: 0x0000000103577f7d XUL`nsNSS_SSLGetClientAuthData(arg=0x000000010ec55fc0, socket=0x000000011d2423d0, caNames=0x000000010b080440, pRetCert=0x000000011afb8640, pRetKey=0x000000011afb8648) + 29 at nsNSSIOLayer.cpp:1992
frame #2: 0x00000001010cec2e libnss3.dylib`ssl3_HandleHandshakeMessage [inlined] ssl3_HandleCertificateRequest(b=0x00000001117de011, length=0, b=0x00000001117de011, b=0x00000001117de011, b=0x00000001117de011, length=0, length=0, length=0, length=0, length=0, ss=<unavailable>) + 72 at ssl3con.c:7111
frame #3: 0x00000001010cebe6 libnss3.dylib`ssl3_HandleHandshakeMessage(ss=0x000000011afb8000, b=<unavailable>, length=<unavailable>) + 10296 at ssl3con.c:11191
frame #4: 0x00000001010d1488 libnss3.dylib`ssl3_HandleRecord [inlined] ssl3_HandleHandshake(ss=0x000000011afb8000, origBuf=<unavailable>) + 241 at ssl3con.c:11305
frame #5: 0x00000001010d1397 libnss3.dylib`ssl3_HandleRecord(ss=0x000000011afb8000, cText=<unavailable>, databuf=0x000000011afb83c0) + 3419 at ssl3con.c:11974
frame #6: 0x00000001010d8a17 libnss3.dylib`ssl3_GatherCompleteHandshake(ss=<unavailable>, flags=<unavailable>) + 1070 at ssl3gthr.c:378
frame #7: 0x00000001010d9505 libnss3.dylib`ssl_GatherRecord1stHandshake(ss=0x000000011afb8000) + 58 at sslcon.c:1227
frame #8: 0x00000001010de265 libnss3.dylib`ssl_Do1stHandshake(ss=0x000000011afb8000) + 90 at sslsecur.c:109
frame #9: 0x00000001010df83f libnss3.dylib`ssl_SecureSend(ss=0x000000011afb8000, buf=0x0000000104f99ea8, len=0, flags=<unavailable>) + 310 at sslsecur.c:1288
frame #10: 0x00000001010e5d82 libnss3.dylib`ssl_Send(fd=<unavailable>, buf=0x0000000104f99ea8, len=0, flags=0, timeout=<unavailable>) + 101 at sslsock.c:2356
frame #11: 0x00000001035770da XUL`PSMSend(fd=0x000000011d242340, buf=0x0000000104f99ea8, amount=0, flags=<unavailable>, timeout=4294967295) + 106 at nsNSSIOLayer.cpp:1448
* frame #12: 0x000000010157329b XUL`nsSocketOutputStream::Write(this=0x0000000123dfbd40, buf=0x0000000104f99ea8, count=0, countWritten=0x000000010b080a54) + 203 at nsSocketTransport2.cpp:612
frame #13: 0x000000010169d599 XUL`mozilla::net::nsHttpConnection::EnsureNPNComplete(this=0x000000011d49db00) + 441 at nsHttpConnection.cpp:306
frame #14: 0x000000010169d800 XUL`mozilla::net::nsHttpConnection::OnSocketWritable(this=0x000000011d49db00) + 160 at nsHttpConnection.cpp:1546
frame #15: 0x00000001016a084c XUL`_ZThn24_N7mozilla3net16nsHttpConnection19OnOutputStreamReadyEP20nsIAsyncOutputStream [inlined] mozilla::net::nsHttpConnection::OnOutputStreamReady(nsIAsyncOutputStream*) + 8 at nsHttpConnection.cpp:2062
frame #16: 0x00000001016a0844 XUL`_ZThn24_N7mozilla3net16nsHttpConnection19OnOutputStreamReadyEP20nsIAsyncOutputStream(this=<unavailable>, out=<unavailable>) + 20 at Unified_cpp_protocol_http1.cpp:2067
frame #17: 0x00000001015730e0 XUL`nsSocketOutputStream::OnSocketReady(this=0x0000000123dfbd40, condition=<unavailable>) + 160 at nsSocketTransport2.cpp:551
frame #18: 0x0000000101576470 XUL`nsSocketTransport::OnSocketReady(this=0x0000000123dfbb80, fd=<unavailable>, outFlags=<unavailable>) + 368 at nsSocketTransport2.cpp:1878
frame #19: 0x000000010157b469 XUL`nsSocketTransportService::DoPollIteration(this=0x0000000100632ea0, wait=<unavailable>, pollDuration=0x000000010b080ca0) + 825 at nsSocketTransportService2.cpp:1085
frame #20: 0x000000010157accc XUL`nsSocketTransportService::Run(this=0x0000000100632ea0) + 492 at nsSocketTransportService2.cpp:867
frame #21: 0x000000010157b73d XUL`_ZThn24_N24nsSocketTransportService3RunEv(this=<unavailable>) + 13 at Unified_cpp_netwerk_base3.cpp:978
frame #22: 0x00000001014d7264 XUL`nsThread::ProcessNextEvent(this=0x0000000100748000, aMayWait=<unavailable>, aResult=0x000000010b080de7) + 1156 at nsThread.cpp:950
frame #23: 0x00000001014fed55 XUL`NS_ProcessNextEvent(aThread=<unavailable>, aMayWait=false) + 53 at nsThreadUtils.cpp:277
frame #24: 0x000000010177ca6c XUL`mozilla::ipc::MessagePumpForNonMainThreads::Run(this=0x0000000100752780, aDelegate=0x00000001006668c0) + 172 at MessagePump.cpp:326
frame #25: 0x0000000101757d4d XUL`MessageLoop::Run() [inlined] MessageLoop::RunInternal(this=<unavailable>) + 77 at message_loop.cc:234
frame #26: 0x0000000101757d3e XUL`MessageLoop::Run() [inlined] MessageLoop::RunHandler(this=<unavailable>) at message_loop.cc:227
frame #27: 0x0000000101757d3e XUL`MessageLoop::Run(this=<unavailable>) + 62 at message_loop.cc:201
frame #28: 0x00000001014d5e56 XUL`nsThread::ThreadFunc(aArg=0x0000000100748000) + 262 at nsThread.cpp:379
frame #29: 0x0000000101203a03 libnss3.dylib`_pt_root(arg=0x0000000100647180) + 211 at ptthread.c:212
frame #30: 0x00007fff8be2b772 libsystem_c.dylib`_pthread_start + 327

So, in e10s mode, let's see if we hit nsNSS_SSLGetClientAuthData in the content process, or in the parent process.

Huh, okay, good, so we hit it properly in the parent process… but we seem to fail out in PK11PasswordPromptRunnable::RunOnTargetThread.

Why… why is that?

Debug build time… hm… mIR in PK11PasswordPromptRunnable::RunOnTargetThread doesn't appear to implement nsIInterfaceRequestor… so we can't get a prompter for it. What? It appears to be a nsNSSSocketInfo*… what is it in the normal case?

We're SUPPOSED to enter TransportSecurityInfo::GetInterface it looks like. Wtf...

HttpChannelParent does not know how to GetInterface or QueryInterface to an nsIPrompt it seems.

ahhh, and we eventually get down to an nsDocShell::GetInterface in the parent process, which does know how to get to an nsIPrompt. Okay.

The nsIPrompt GetInterface for nsDocShell was added wayyyy back in 2000: https://github.com/mozilla/gecko-dev/commit/ee63f155b169e33363653f0abfd31549f6801ed3

Okay, welp, patch written. Let's see what michal from the Necko team thinks.

Try push: https://treeherder.mozilla.org/#/jobs?repo=try&revision=c90ce9609371

Bill thinks this patch is r+, if I can explain why NSS needs to have the HttpChannelParent… uh...

Well, let's see. The wincx is what we GetInterface. And in single-process Firefox, it's nsDocShell… and in multi-process, it's HttpChannelParent.

Looking at nsNSSIOLayer.cpp, which eventually calls into the thing that GetInterface's nsIPrompt...

https://dxr.mozilla.org/mozilla-central/rev/6256ec9113c115141aab089c45ee69438884b680/security/manager/ssl/nsNSSIOLayer.cpp#2054

void
ClientAuthDataRunnable
:: RunOnTargetThread()
{
PLArenaPool
* arena = nullptr;
char ** caNameStrings;
ScopedCERTCertificate cert;
ScopedSECKEYPrivateKey privKey;
ScopedCERTCertList certList;
CERTCertListNode
* node;
ScopedCERTCertNicknames nicknames;
int keyError = 0 ; // used for private key retrieval error
SSM_UserCertChoice certChoice;
int32_t NumberOfCerts = 0 ;
void * wincx = mSocketInfo;
nsresult rv;

Okay, so that's where the wincx is defined. The ClientAuthDataRunnable has an mSocketInfo… and that's what we use. What is an mSocketInfo?

According to this: https://dxr.mozilla.org/mozilla-central/rev/6256ec9113c115141aab089c45ee69438884b680/security/manager/ssl/nsNSSIOLayer.cpp#1953

It's a nsNSSSocketInfo instance.

Mmmkay… so an nsDocShell is an nsNSSSocketInfo?

Uh...

So it looks like TransportSecurityInfo forwards its GetInterface to its mCallbacks… and its mCallbacks seem to be inherited from nsHttpChannel::OnPush's mCallbacks...

And mCallbacks is set via nsBaseChannel::SetNotificationCallbacks…

Welp, here’s what I wrote to billm:

"It's a bit convoluted, but I think this is how it goes.

So we have these nsHttpChannels, and these inherit from nsBaseChannel. nsBaseChannel has a protected member called mCallbacks which is set via nsBaseChannel::SetNotificationCallbacks in... a number of places.

For HttpChannelParent, SetNotificationCallbacks is called here: https://dxr.mozilla.org/mozilla-central/rev/79a5b2968d01512470eb6c25d6638d8b9565575e/netwerk/protocol/http/HttpChannelParent.cpp#423

The callback it passes is an HttpChannelParentListener, which implements nsIInterfaceRequestor, and forwards GetInterface to the HttpChannelParent.

Fast-forward to when we want to show the user the certificate dialog.

The network layer eventually figures out that we're doing TLS client auth, and calls nsNSS_SSLGetClientAuthData: https://dxr.mozilla.org/mozilla-central/rev/79a5b2968d01512470eb6c25d6638d8b9565575e/security/manager/ssl/nsNSSIOLayer.cpp#1988

It then instantiates a ClientAuthDataRunnable (this is what's going to show the dialog): https://dxr.mozilla.org/mozilla-central/rev/79a5b2968d01512470eb6c25d6638d8b9565575e/security/manager/ssl/nsNSSIOLayer.cpp#2023

When constructing that runnable, it passes "info", which in the case for e10s, happens to be a TransportSecurityInfo::TransportSecurityInfo.

A reference to that TransportSecurityInfo is stored inside the ClientAuthDataRunnable as mSocketInfo, which when the runnable is fired, is cast to a void* pointer here: https://dxr.mozilla.org/mozilla-central/source/security/manager/ssl/nsNSSIOLayer.cpp#2054 and passed around as "wincx".

There's quite a bit of indirection, but eventually that void* wincx pointer is cast back to an nsIInterfaceRequestor here when we decide we want to open up the master password dialog: https://dxr.mozilla.org/mozilla-central/rev/79a5b2968d01512470eb6c25d6638d8b9565575e/security/manager/ssl/nsNSSCallbacks.cpp#865

We then GetInterface for nsIPrompt off of the nsIInterfaceRequestor.

So, to sum, that nsIInterfaceRequestor eventually gets forwarded to the TransportSecurityInfo, which eventually forwards the GetInterface to the HttpChannelParent.

Those "eventually"'s can be pretty long and complex, and take me into the guts of netwerk. It's quite extraordinary. So this is my high-level understanding of how HttpChannelParent got into NSS. If you need me to go deeper, let me know."

And he liked it! Landed!