Skip to main content

SSL issues in the ingame browser

EVE Online has an ingame browser, and under Wine that browser has issues with opening some websites using https. Those sites work in the game under Windows, so I knew it wasn't a browser issue per se. It wasn't an issue with all sites using https, either, so it wasn't a matter of SSL not working at all, either.

With the help of CCP's security expert, we noticed that the sites that were failing had certificate chains up to a root certificate with a very strong signature algorithm, ecdsa-with-SHA384, and chances were that Wine did not support that particular algorithm.

Now what?

Personally I'm no expert in security algorithms, SSL or TSL or anything like that, so I wasn't sure where to even begin looking at Wine source code to see if this algorithm was supported.

After some digging around I decided to look at the output of the secur32 channel:
export WINEDEBUG=+secur32

Then I started up the EVE client and opened up the browser, entering https://zkillboard.com in the address bar. There was quite a lot of output - no errors or warnings, though, and no smoking gun to be found. The functions in secur32 are very low level, though, and I found they were being called from functions in crypt32. Adding the crypt channel to the debug output would in theory give a clearer view of what was going on, but now I had the problem that the log was up to 800k lines.

I disabled the code that gets the root certificates from the system and pointed it to a bundle read from disk - I could then trim that bundle to only include the root certificate used by this particular site - Comodo ECC. This resulted in a log of around 10k lines - somewhat more manageable. Now I was able to confirm that this root certificate did not import properly due to a bad signature. Adding a bit more detailed logging confirmed that the bad signature stemmed from signature algorithm not being recognized.

But it works on Linux

I spent a bit of time trying to figure out what would be needed to add support for this signature algorithm - ecdsa-with-SHA384, but after a day or so realized that this was a rabbit hole I probably shouldn't be going down. I also found out that this troublesome site seemed to work fine on Linux. Feeling stupid for not checking that sooner, I compiled Wine on my Ubuntu box from the same repo and tested this. Sure enough, https://zkillboard.com loaded up just fine in the ingame browser.

Well, great, I thought, I can use the implementation for Linux as a reference to get this to work on OS X. When looking through the code I quickly found that there was no support for this algorithm, or any ECC certificates on Linux either. Pouring over the logs from Linux also revealed that this same root certificate failed to import, so there had to be some other way for that site to get certified.

Consider the alternatives

We took another look at zkillboard.com and found that there was an alternate chain, with a root certificate with a more typical RSA type signature. The question still remained, though - why did Wine on OS X not validate the alternate certificate chain when Wine on Linux did?

I added the Comodo RSA root certificate to the bundle I was using on the Mac, and it imported fine. Still the site wouldn't work. I figured there had to be some issue with finding that alternate chain, but as I knew very little about SSL in general this was a bit of a needle in a haystack situation. Being stubborn I figured I had no other option than digging into the Wine source, trying to understand what was going on.

I started tracing through CertGetCertificateChain in dlls/crypt32/chain.c, trying to understand how that worked and why it wouldn't accept the alternate chain we knew was available for this site. After adding more detailed logging I was convinced that this code was simply not getting an alternate chain. Why did this work on Linux, then?

The smoking gun

I went back to the Linux machine, ran the EVE client with the secur32 and crypt channels enabled and started analyzing the logs. Finally I noticed that the handshake was logged in some detail.

trace:secur32:schan_gnutls_log <4> HSK[0x7d416a18]: SERVER KEY EXCHANGE (12) was received. Length 327[327], frag offset 0, frag length: 327, sequence: 0
trace:secur32:schan_gnutls_log <4> HSK[0x7d416a18]: Selected ECC curve SECP256R1 (2)
...
trace:secur32:schan_gnutls_log <4> HSK[0x7d416a18]: CLIENT KEY EXCHANGE was queued [70 bytes]
trace:secur32:schan_gnutls_log <4> REC[0x7d416a18]: Sent ChangeCipherSpec

trace:secur32:schan_gnutls_log <4> HSK[0x7d416a18]: Cipher Suite: ECDHE_RSA_AES_128_CBC_SHA1

Maybe this would have been obvious to somebody that knows SSL well, but I hadn't realized that the server decides what certificate chain to send to the client. I guess it sends the strongest chain it has, unless the client specifically asks for a weaker cipher. I finally felt I was getting somewhere - now I just had to figure out how do the same on the Mac.

A secure session starts in InitializeSecurityContext, which is implemented in dlls/secur32/schannel.c. It calls schan_imp_create_session, which is in dlls/secur32/schannel_macosx.c - that function uses functions provided from OS X - SSLNewContext, for example, then later on SSLHandshake is called. I got a bit worried that since OS X handled the handshake I wouldn't be able to affect it, but a bit of searching led me to SSLSetEnabledCiphers.

A happy ending

To cut a long story short, calling that function with an array of cipher suites that Wine really supports, i.e. excluding the Elliptic Curve cipher suites solved the issue. That site that caused me such grief now finally loaded in the ingame browser.

Now I just need to clean this up a bit and submit a patch!

Popular posts from this blog

Mnesia queries

I've added search and trim to my expiring records module in Erlang. This started out as an in-memory key/value store, that I then migrated over to using Mnesia and eventually to a replicated Mnesia table. The fetch/1 function is already doing a simple query, with match_object. Result=mnesia:match_object(expiring_records, #record{key=Key, value='_', expires_at='_'}, read) The three parameters there are the name of the table - expiring_records, the matching pattern and the lock type (read lock). The fetch/1 function looks up the key as it was added to the table with store/3. If the key is a tuple, we can also do a partial match: Result=mnesia:match_object(expiring_records, #record{key= {'_', "bongo"}, value='_', expires_at='_'}, read) I've added a search/1 function the module that takes in a matching pattern and returns a list of items where the key matches the pattern. Here's the test for the search/1 function: search_partial_…

Waiting for an answer

I want to describe my first iteration of exsim, the core server for the large scale simulation I described in my last blog post. A Listener module opens a socket for listening to incoming connections. Once a connection is made, a process is spawned for handling the login and the listener continues listening for new connections. Once logged in, a Player is created, and a Solarsystem is started (if it hasn't already). The solar system also starts a PhysicsProxy, and the player starts a Ship. These are all GenServer processes. The source for this is up on GitHub: https://github.com/snorristurluson/exsim Player The player takes ownership of the TCP connection and handles communication with the game client (or bot). Incoming messages are parsed in handle_info/2 and handled by the player or routed to the ship, as appropriate. The player creates the ship in its init/1 function. The state for the player holds the ship and the name of the player. Ship The ship holds the state of the ship - …

Replicated Mnesia

I'm still working on my expiring records module in Erlang (see here and here for my previous posts on this). Previously, I had started using Mnesia, but only a RAM based table. I've now switched it over to a replicated disc based table. That was easy enough, but it took a while to figure out how to do, nonetheless. I had assumed that simply adding ... {disc_copies, [node()]} ... to the arguments to mnesia:create_table would be enough. This resulted in an error: {app_test,init_per_testcase, {{badmatch, {aborted, {bad_type,expiring_records,disc_copies,nonode@nohost}}}, ... After some head-scratching and lots of Googling I realized that I was missing a call to mnesia:create_schema to allow it to create disc based tables. My tests for this module are done with common_test so I set up a per suite initialization function like this: init_per_suite(Config) ->mnesia:create_schema([node()]), mnesia:start(…