Thursday, July 16, 2015

IE9 versus PCI compliance

Nerd speak: Starting July 2016, HTTPS communication of confidential information must be negotiated as TLSv1.1+ for PCI DSSv3.1 compliance.

Layman speak: Your Grandma's computer won't be able to buy stuff from Etsy.  (This effectively means the web browser that first shipped in 2011 with Windows Vista, IE9, will no longer be supported for credit card payments.)

Sincerely,

Your Paranoid Computer Nerds

Migrating_from_SSL_Early_TLS_Information Supplement

Thursday, July 9, 2015

HTTPS: What Nobody Told Us

I burned one too many hours troubleshooting an HTTPS issue and decided to share lessons learned.  Both programmatic no-no's and TLS details that nobody told us.

The Audit

This story started when some auditor got in a frenzy that TLSv1.0 was allowed for public HTTPS communication with a customer's web application.  That auditor demanded that only TLSv1.1 or v1.2 be allowed despite version 1.0's problem being isolated to weak CBC and RC4 ciphers (aka. the infamous BEAST attack) -- ciphers that we already weren't allowing.

Side rant: who has time for audits with little security justification?  I've no patience for security "gurus" who cannot run a simple scan to verify their worst nightmare or best dream:

justin:tmp jpittman$ nmap --script ssl-enum-ciphers $HOSTNAME

Starting Nmap 6.40-2 ( http://nmap.org ) at 2015-07-08 11:12 CDT
...
PORT    STATE SERVICE
80/tcp  open  http
443/tcp open  https
| ssl-enum-ciphers: 
|   SSLv3: No supported ciphers found
|   TLSv1.0: 
|     ciphers: 
...

OK fine, assuming the auditor's request is legit, I disable TLSv1.0 at load balancers terminating HTTPS ... and the webapp breaks.  

For better disclosure, I should clarify secure communication in this particular design.  This was a typical, 3-tier application architecture that was designed so that a layer of load balancers would proxy requests between the set of front-end tiers, aka. on behalf of web servers and app servers, yet there was also intra-network communication between application servers that hosted different apps that these load balancers also proxied.  This design simplified HTTPS termination because only the load balancers acting as proxies needed to have their secure certificates managed, however it did complicate the idea of one app "server" making a client call to another app server within the same network.  Also, these deployed webapps were all Java based -- but programming language really only matters for details in implementation.

The Error

When I turned off TLSv1.0 on all the load balancers, one of the deployed Java apps acting as a web client started throwing errors about SSL handshaking, like this:

IOException when getting the response content input stream javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake

Here's the first part of the story that nobody told us: misnomers.  This error makes it sound like an SSL protocol is failing when, as I'll show later, it is a TLS protocol failure.  And this misnomer isn't particular to Java.  I checked Python, Ruby, and PHP only to find that they too negotiate TLS protocols via inaccurately named "SSL" libraries or methods.  Sure, you can make a defense for using the term "SSL/TLS" in documentation but what a way to conjure up a red herring.  I wasted cycles thinking SSL protocols were still in play when they never were!  Shame on you! 

A Secure Socket  

By digging into Java code samples, I found some ideal tests for uncovering the root cause of this ill-named handshake error:

SSLContext context = SSLContext.getInstance("TLSv1.2");

Now I'm no Java programmer but this bit of code is fairly simple.  It says the SSL/TLS context, so in this case a client call, will be gotten with a parameter that sets the TLSv1.2 protocol.  That client context creates a socket to some server -- hence the acronym Secure Socket Layer (SSL) of the original, now inaptly named class SSLContext.  When I tested this code with the parameter TLSv1.2 the proxied connection to the problematic server worked, but when set to TLSv1.0 the connection failed with the above SSL handshake error.  Ahah!

TL;DR


Some good ol' TL;DR documentation verified default Java behavior that would explain the errors too.  This case used Oracle's Hotspot JVM and luckily that vendor's documentation is usually verbose, if not also cryptic.  I read Oracle's rather lengthy reference guide to Java Secure Socket Extension (JSSE) that covered both those SSLContext and HttpsURLConnection classes.  First off, picking the correct version of the documentation avoided some false fixes.  Java 8 fixes didn't apply for a case of Java 7, as this was.  Next, the mode of the JVM as client versus server altered its default behavior.  Oracle said JVM clients enable a different set of protocols and versions than those in server mode.  Also, the documented samples set SSLContext to "TLS" inline -- which I would assume could mean any version of TLS -- yet the documentation clearly says that "TLS" means "TLSv1.0", excluding v1.1 or v1.2.  If a lazy programmer didn't read that documentation yet borrowed its code samples, then she would have actually hardcoded the client to TLSv1.0 versions.  Finally, the SSL handshake error would occur if the web client used an SSLContext method that set buildtime configurations of the protocol.  

So hardcoded bug or default behavior? 

A Web Client

A similar bit of Java code shed more light on the common world of this web client problem:


url = new URL(https_url);

HttpsURLConnection con = (HttpsURLConnection)url.openConnection();

In this code, an HTTPS connection is created to a URL of some server, aka. a classic web client.  Yet this HttpsURLConnection method doesn't specify the protocol or version like the previous spinnet of code.  I had read that HttpsURLConnection honors the JVM runtime option https.protocols to change protocols and versions.  Here is where runtime versus buildtime components revealed themselves as part of the problem.  By testing the means to setup a secure web client via two different methods -- socket versus URL -- it became dramatically clear that I was probably dealing with a classic, hardcoded bug.  

I reconfigured the runtime of this HTTPS connection to use TLSv1.1 or v1.2 and my Java test client worked!

Hardcoded / Buildtime Configuration  

A developer confirmed the root cause was indeed a hardcoded setting of the protocol and version when he changed the problematic webapp client's SSLContext parameter to "TLSv1.2", rebuilt, redeployed.  Although the fix was simple, I had burned too many hours troubleshooting -- what was essentially -- a hardcoded / buildtime problem that could not be trivially fixed with runtime changes.  I had followed some red herrings while searching for the root cause of this seemingly simple SSL/TLS change, including:

  • Server/Client Certificates
  • Certificate Authority chains
  • Firewalls

So nerds beware.  You may need to go down the rabbit hole far beyond the typical diagrams with labels like "SSL Handshake".

References

https://tersesystems.com/2014/01/13/fixing-the-most-dangerous-code-in-the-world/
http://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/JSSERefGuide.html
https://blogs.oracle.com/java-platform-group/entry/diagnosing_tls_ssl_and_https
http://www.oracle.com/technetwork/java/javase/documentation/cve-2014-3566-2342133.html
http://docs.oracle.com/javase/7/docs/technotes/tools/solaris/keytool.html