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 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, 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 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

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: 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 - …

Large scale ambitions

Learning new things is important for every developer. I've mentioned this before, and in the spirit of doing just that, I've started a somewhat ambitious project.

I want to do a large-scale simulation, using Elixir and Go, coupled with a physics simulation in C++. I've never done anything in Elixir before, and only played a little bit with Go, but I figure, how hard can it be?

Exsim I've dubbed this project exsim - it's a simulation done in Elixir. Someday I'll think about a more catchy name - for now I'm just focusing on the technical bits. Here's an overview of the system as I see it today:

exsim sits at the heart of it - this is the main server, implemented in Elixir. exsim-physics is the physics simulation. It is implemented in C++, using the Bullet physics library. exsim-physics-viewer is a simple viewer for the state of the physics simulation, written in Go. exsim-bot is a bot for testing exsim, written in Go. exsim-client is the game client, for inter…

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_…