HTTP and TLS – followup

A few weeks ago I wrote about each SSL certificate used on a web server needing to have its own IP address. I ran across a reference to RFC 2817, Upgrading to TLS Within HTTP/1.1. It describes a method to upgrade a connection to TLS (Transport Layer Security, the successor to SSL; I’ll use the terms interchangeably). It’s somewhat complex, and does indeed expand the request/response model along the same lines as basic authentication while taking advantage of HTTP/1.1 persistent connections.

Of course, just because something is described in an RFC doesn’t mean it’s being used. Apache has TLS Upgrade support as of 2.2, but at this time none of the browsers (at least the major ones) support the protocol. Even though it’s not really in use, it’s an interesting exercise to look at how it’s defined and think about implications of its use.

On the Apache side, you normally use SSLEngine to enable SSL. However, instead of using either On or Off, using Optional will enable connection upgrades. If you want to force TLS upgrades, you can also use SSLRequireSSL in a directory block. Why would you want to do this instead of just using SSLEngine On? Were browsers to support it, you could then have multiple virtual servers on the same IP address and still use name-based virtual hosting since the name would be part of the initial request before the SSL certificate is sent from the server to the browser. This is how the transaction looks when a client offers to upgrade to SSL and the server accepts:

Client   Server

Send request indicating it’s OK to upgrade to SSL

GET /url HTTP/1.1
Host: www.a.com
Upgrade: TLS/1.0
Connection: Upgrade
 
   

Server responds it will upgrade

HTTP/1.1 101 Switching Protocols
Upgrade: TLS/1.0, HTTP/1.1
Connection: Upgrade
 Initiate SSL handshake    

If the server will require SSL, it will return 426 Upgrade Required instead of 101 Switching Protocols. All other requests on the same connection (i.e. keepalives or pipelining) will be encrypted since the SSL connection will have already been set up. Once the SSL connection is established, the server will continue with the response to the original request.

Note that the URL in the request is being sent unencrypted, as would any data sent (such as with a POST request). If the client wishes the whole transaction to be encrypted, it must first send an OPTIONS request (such as OPTIONS *), which has the effect of not sending any unwanted information before the encrypted channel is established.

At the same time, the Host header is also sent before the SSL handshake begins, which allows Apache to select the correct virtual server, and the correct SSL certificate which goes with it. You can therefore have multiple SSL certificates associated with the same IP address.

If the server does not support TLS upgrading, it will respond as if the client did not request encryption. This behavior explains the reasoning for having the client send an OPTIONS request if encryption is required by the client, since neither the request nor the response would contain sensitive information.

A server which is configured to handle TLS Upgrade will not initiate the SSL handshake until after the upgrade request has been made, so virtual servers answering on port 443 (for https: URLs) should continue to use SSLEngine On.

Enable SSL for each connection

The client will need to go through the TLS upgrade for each connection, but not for every request. The upgrade protocol is built on top of HTTP/1.1 which can reuse the same connection for back-to-back requests. Servers will close the connection after so often (we set high-volume servers to one second), but at least for getting the initial page, there won’t be a whole lot of overhead with this mechanism.

Pages which do out-of-band requests (such as for AJAX) will need to either renegotiate the encryption upgrade for every request or use an SSL-only URL.

Proxies

The TLS upgrade function is pretty straightforward when the client and server are directly communicating, but introducing proxy servers complicates the situation, since the Upgrade header is meant to be negotiated between each hop, so the above would upgrade to TLS between the client and the proxy and not between the proxy and the final server. However, encrypted connections need to be made directly between the client and the final server so the certificate can be verified. When going through proxies, the client should issue a CONNECT request to the proxy, which in turn will establish a tunnel on behalf of the client. If there are multiple proxies, each pair should establish a tunnel with a CONNECT request. Once that’s done, the client can then start the TLS handshake with the final server.

Reverse proxies act differently than regular proxies, and the client does the TLS negotiation directly with the reverse proxy. This is why a reverse proxy must be the server which has the correct SSL certificate.

Client behavior

It seems pretty clear how a client should act if the server offers a TLS upgrade with RFC 2817. A browser would respond to the upgrade request and show the lock icon indicating a secure connection. It may be confusing to users who are used to seeing https: for encrypted connections, and it’s pretty evident that the browser shouldn’t falsely rewrite the URL’s method.

What’s not clear is how a browser should allow a user to optionally upgrade to an encrypted connection. One person suggested that instead of being a status icon, the padlock should be a button that the user could enable. However, the selection of encryption should, in some cases, happen before the user has visited the site. It’s not evident to the client whether the server will offer upgrading to an encrypted channel until the first (or dummy, such as OPTIONS) request has been made.

I suppose one way around the problem is for the browser to always offer try to upgrade the connection and then cache the server’s response. In other words, always use TLS when possible, but don’t keep trying if you know the server doesn’t handle it.

Leave a Reply