NEW 158345
[websocket] does not send client certificate
https://bugs.webkit.org/show_bug.cgi?id=158345
Summary [websocket] does not send client certificate
Adi Stadelmann
Reported 2016-06-03 06:47:47 PDT
Using a client certificate for a secure websocket connection, SSL Handshake fails because client certificate was not send. Tested with latest nightly WebKit-SVN-r201640 OS X 10.11.5 (15F34) Https works, firefox, chrome and migrosoft edge works too. Openssh handshake log: ``` verify depth is 9, must return a certificate Using default temp DH parameters Using default temp ECDH parameters ACCEPT SSL_accept:before/accept initialization SSL_accept:SSLv3 read client hello A SSL_accept:SSLv3 write server hello A SSL_accept:SSLv3 write certificate A SSL_accept:SSLv3 write certificate request A SSL_accept:SSLv3 flush data SSL3 alert write:fatal:handshake failure SSL_accept:error in SSLv3 read client certificate B SSL_accept:error in SSLv3 read client certificate B ERROR 14774:error:140890C7:SSL routines:SSL3_GET_CLIENT_CERTIFICATE:peer did not return a certificate:/BuildRoot/Library/Caches/com.apple.xbs/Sources/OpenSSL098/OpenSSL098-59.40.2/src/ssl/s3_srvr.c:2640: shutting down SSL CONNECTION CLOSED ACCEPT ```
Attachments
Adi Stadelmann
Comment 1 2016-06-06 00:50:21 PDT
How to reproduce: // ca openssl genrsa -des3 -out ca.key 4096 openssl req -new -x509 -days 365 -key ca.key -out ca.crt // server cert openssl genrsa -out server.key 4096 openssl x509 -req -sha256 -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt // client cert openssl genrsa -out client.key 2048 openssl req -new -key client.key -out client.csr openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt // p12 for import openssl pkcs12 -export -clcerts -inkey client.key -in client.crt -out myClientCert.p12 import ca.crt and myClientCert.p12 into keychain, modify both to trust all debugging with openssl: sudo openssl s_server -accept 443 -key server.key -cert server.crt -CAfile ca.crt -Verify 9 -state Connect with (webkit javascript console): new WebSocket('wss://localhost/test');
andi
Comment 2 2017-01-20 12:48:49 PST
Simular problems here – are there any updates on this issue?
jonas
Comment 3 2018-01-15 23:17:03 PST
Any chance to get that bug fixed soon? No dev attention since 2016... Would be great to see any state change!
bugzilla.josh
Comment 4 2018-05-25 20:56:23 PDT
Also very interested in seeing this fixed. Really cripples secure realtime ios webapps
Radar WebKit Bug Importer
Comment 5 2018-07-11 07:53:23 PDT
Tor Arne Vestbø
Comment 6 2018-07-12 10:17:33 PDT
Poking a bit a bit at this, it looks like normal loads are fed through WKNetworkSessionDelegate in NetworkSessionCocoa.mm in the Network process, where [WKNetworkSessionDelegate URLSession:task:didReceiveChallenge:completionHandler:] gets a callback with NSURLAuthenticationMethodClientCertificate, which is passed on to the UIProcess via NetworkLoad::didReceiveChallenge() and NetworkProcess::singleton().authenticationManager().didReceiveAuthenticationChallenge(), coming back from the UIProcess via AuthenticationManager::useCredentialForChallenge(), finally ending up calling the completion handler of the original callback. WebSocket connections are also handled in the Network process, via SocketStreamHandle and SocketStreamHandleImpl in SocketStreamHandleImplCFNet.cpp, which sets up the connection in SocketStreamHandleImpl::createStreams(). There is some logic there for credentials via SocketStreamHandleImpl::getStoredCONNECTProxyCredentials, but nothing for client certificates. Presumably the fix here is to set the kCFStreamPropertySSLSettings property on the CFWriteStreamRef to a dict that includes the cert for the the kCFStreamSSLCertificates key. The question is how to plumb a previously accepted cert on the UI side to the cert on the network process side. The normal load works via a callback, while CFStream seems to require the property to be set up front. There doesn't seem to be any way to peek into the cert negotiation and only ask for the cert and set the property at that point. Alex, any thoughts? One thing to note, is that the original bugreport described the reproducer as just connecting via new WebSocket('wss://localhost/test');, but this is not supported in Chrome either. You need to do a normal page load first, and then do websocket requests after accepting the cert in the UI. Running the reproducer with s_server -HTTP should make that easy to test by first loading a file on disk and then doing the JS bit within the loaded page. For reference, I couldn't get Chrome or Safari to accept the self-signed server cert without a SAN. Here's what I used instead: openssl req -x509 -nodes -new -out server.crt -keyout server.key -subj /CN=localhost -reqexts SAN -extensions SAN -config <(cat /etc/ssl/openssl.cnf && printf '[SAN]\nsubjectAltName=DNS:localhost,IP:127.0.0.1') -sha256 -days 3650
Alex Christensen
Comment 7 2018-07-12 10:49:50 PDT
I think the best way to solve this would be to re-implement WebSockets using NSURLSessionStreamTask instead of CFReadStreamRef/CFWriteStreamRef. That's a substantial amount of work with high regression risk.
Tor Arne Vestbø
Comment 8 2018-07-12 11:04:56 PDT
Yeah, I suspected something like that :) Pending someone doing that work (who know's what they are doing, to lessen the risk), is there any way to plumb the two worlds together within the current architecture? There are workarounds on the server side, at least when doing reverse proxying with ngnix [1], but they are not pretty, so it would be nice to have an upstream fix for macOS/iOS Safari. [1] https://blog.christophermullins.com/2017/04/30/securing-homeassistant-with-client-certificates/
Tor Arne Vestbø
Comment 9 2018-07-12 11:06:33 PDT
Presumably this issue also affects iOS apps using WKWebView (assuming the app has imported the client cert into the app's keychain)?
tobias
Comment 10 2020-04-11 03:06:56 PDT
Is there any timeline for a bugfix, the bug is open for almost 4 years now?
youenn fablet
Comment 11 2020-04-11 07:30:30 PDT
There is ongoing work to use NSURLSession WebSocket API instead of the internal WebSocket implementation. This should hopefully fix this issue. An early version is available in Safari Tech Preview on recent MacOSX: Develop -> Experimental Features -> "NSURLSession WebSocket".
tobias
Comment 12 2020-04-12 04:57:26 PDT
Awesome, thanks for the tip. The experimental feature can even be enabled in the already released Safari 13.1 and in iOS 13.4.1. Activating seems to work fine for the sites I tested. Looking forward to the feature being integrated.
Alexey Proskuryakov
Comment 13 2020-04-12 17:21:43 PDT
Please feel encouraged to file bugs about any new issues that you may encounter with the experimental implementation. As Alex said back in 2018, there is a high regression risk.
Andreas Jung
Comment 14 2020-05-24 05:31:23 PDT
Tested client cert websocket with the NSURLSession WebSocket in iOS 13.5 It works once per browser start - after I hit refresh it will no longer connect. Restart browser app - it works again until refresh. (Blazor Webassembly / SignalR with Websocket only)
Dirkjan Bussink
Comment 15 2020-07-14 05:11:46 PDT
> There is ongoing work to use NSURLSession WebSocket API instead of the internal WebSocket implementation. This should hopefully fix this issue. We found that if this feature is enabled on the Big Sur or iOS 14 betas, it breaks websockets in a problematic way. In these new betas, support was added for per message deflate on a websocket connection but the implementation for that is broken it looks like. https://github.com/dbussink/big-sur-websocket-bug contains a standalone Swift reproduction for this plus some network captures of the broken behavior. Tl;dr, if a websocket server doesn't support compression, the client enables it nonetheless which is a protocol violation. If the server does accept compression, the client still ends up in an invalid state on messages sent to the server after the first one (slightly different behavior if context takeover is enabled vs. not). I've also reported this at https://feedbackassistant.apple.com/feedback/7970295 but not sure if there's a better channel for it then.
Brian Peiris
Comment 16 2021-07-23 02:45:27 PDT
I'm glad NSURLSession WebSocket is a viable workaround for this issue, but I'd also like to see it finalized. This issue is a blocker for developing webapps that need to use both WebSockets and protected APIs like getUserMedia, since those aren't available on insecure origins. Self-signed certs are the only option.
Note You need to log in before you can comment on or make changes to this bug.