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

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

Working with Xmpp in Python

Xmpp is an open standard for messaging and presence, used for instant messaging systems. It is also used for chat systems in several games, most notably League of Legends made by Riot Games.

Xmpp is an xml based protocol. Normally you work with xml documents - with Xmpp you work with a stream of xml elements, or stanzas - see for the full definitions of these concepts. This has some implications on how best to work with the xml.

To experiment with Xmpp, let's start by installing a chat server based on Xmpp and start interacting with it. For my purposes I've chosen Prosody - it's nice and simple to install, especially on macOS with Homebrew:

brew tap prosody/prosody
brew install prosody

Start the server with prosodyctl - you may need to edit the configuration file (/usr/local/etc/prosody/prosody.cfg.lua on the Mac), adding entries for prosody_user and pidfile. Once the server is up and running we can start poking at it to get a feel for h…

Expiring records in Erlang

I'm continuing my experiments with Erlang - this time trying out gen_server with a simple key/value store with a twist - the values have an expiration date. As a first iteration I'm simply using a dictionary to store the values, and only expiring records when they are looked up. My plan is to extend this later on so that this can be a global key/value store across multiple Erlang nodes but for now I'm focusing on two things - get something going using gen_server, and try out the common_test testing framework. The code is here: Let's first take a look at a couple of the test functions, to show the usage of this: get_non_expired_record(Config) ->Pid=?config(pid, Config), Record= {"bingo", "bongo", erlang:system_time(second) +3600}, ok=gen_server:call(Pid, {add, Record}), {ok, "bongo"} =gen_server:call(Pid, {fetch, "bingo"}). get_expired_record(Config) ->Pid=…