2012-07-19

SPDY In-Session Key Negotiation

For those who may have missed it, there is currently work underway to define the next iteration of the fundamental HTTP protocol [1][2]... One of the key work items seeding this effort is the SPDY protocol... and one of the key proposals being put on the table by proponents of the SPDY protocol is mandatory support for Transport Layer Security (TLS)... this extremely controversial idea is intended to enforce Privacy-By-Default for the web. Many good arguments for and against the proposal are being put forward and it's obvious that there is not yet a solid consensus one way or the other (prompting the WG chair to impose a bit of a cooling off.... 

Personally, I'm of the opinion that use of TLS should be optional and that exploration of alternative options should be opened up. This blog post documents once such possible alternative. I would normally publish this as an Internet-Draft but due to the coming up IETF meeting publishing of new drafts is closed until the end of the month. So rather than allow this to get lost in the flood of posts on the list or go undocumented, here you go...

A Bit of Background... SPDY Proxies

Before you read further, go here and read this: http://dev.chromium.org/spdy/spdy-proxy-examples

In that page, you will see numerous examples of a browser talking to various remote services via a secure proxy. The remote services can be HTTP, HTTPS or SPDY; communication between the browser and the proxy is always over TLS.

Here's an example of one of the illustrated flows that shows a browser first establishing a TLS Tunnel via a proxy, then establishing a second TLS session via the proxy with the origin server...

Client              HTTPS Proxy            Origin Server
 ------              -----------            -------------
*** If an available tunnel to the origin exists, jump to step 19 ***
*** If an available connection to the proxy exists, jump to step 9 ***
*** Client opens TCP connection to Proxy ***
   |---------------------->|                      |
   | 1) SYN                |                      |
   |                       |                      |
   |<----------------------|                      |
   | 2) SYN_ACK            |                      |
   |                       |                      |
   |---------------------->|                      | 
   | 3) ACK                |                      |
   |                       |                      |
*** Client performs SSL handshake with Proxy ***
   |---------------------->|                      |
   | 4) Client Hello       |                      |
   |                       |                      |
   |<----------------------|                      |
   | 5) Server Hello       |                      |
   |                       |                      |
   |---------------------->|                      |
   | 6) Client Key Exchange|                      |
   |                       |                      |
   |---------------------->|                      |
   | 7) Client Finished    |                      |
   |                       |                      |
   |<----------------------|                      |
   | 8) Server Finished    |                      |
   |                       |                      |
*** Client sends CONNECT request to Proxy via SPDY ***   
    ---------------------->|                      |
   |9) CONNECT origin:443  |                      |
   |                       |                      |
   |                    *** Proxy connects (or reuses connection) to origin ***
   |                       |--------------------->|
   |                       |10) SYN               |
   |                       |                      |
   |                       |<---------------------|
   |                       |11) SYN_ACK           |
   |                       |                      |
   |                       |--------------------->|
   |                       |12) ACK               |
   |                       |                      |
*** Proxy sends HTTP CONNECT response to client ***
   |<----------------------|                      |
   |13) HTTP/1.1 200 Connection Established       |
   |                       |                      |
*** Client performs SSL handshake with Origin (through proxy tunnel) ***
   |=============================================>|
   |14) Client Hello                              |
   |                                              |
   |<=============================================|
   |15) Server Hello                              |
   |                                              |
   |=============================================>|
   |16) Client Key Exchange                       |
   |                                              |
   |=============================================>|
   |17) Client Finished                           |
   |                                              |
   |<=============================================|
   |18) Server Finished                           |
   |                                              |
*** Client issues request via SPDY to origin ***
   |=============================================>|
   |19)SYN_STREAM                                 |
   |   stream_id = 1                              |
   |   method = GET                               |
   |   url = /                                    |
   |   host = origin                              |
   |                                              |
   |*** Origin sends SPDY response to Client ***
   |<=============================================|
   |20)SYN_REPLY                                  |
   |   stream_id = 1                              |
   |   status = 200 OK                            |
   |                                              |

Yes, that ends up tunneling encrypted TLS traffic over an encrypted TLS connection

An alternative approach would be to introduce an In-Session Key Negotiation mechanism directly to SPDY...

The KEY_NEGO Frame

The idea is to add a new type of Control frame to SPDY whose purpose is for the negotiation of keys within a SPDY session.

   +----------------------------------+
   |1|   version |     KEY_NEGO       |
   +----------------------------------+
   | Flags (8)  |   ID (8) | ALG_ID(8)|
   +----------------------------------+
   | Length(16) |      Data           |
   +----------------------------------+

Each KEY_NEGO Frame would have an ID, and algorithm, and associated data, the structure and content of which is dependent on the algorithm ID.  

Key Negotiation within a SPDY session involves exchange of 1 or more KEY_NEGO frames and can be initiated by either side of the connection. The specific number of roundtrips required depends on the algorithm selected.

KEY_NEGO frames initiated by the server must have an even-numbered ID
KEY_NEGO frames initiated by the client must have an odd-numbered ID.

Multiple keys could be negotiated within a single session but once the range of possible ID's is exhausted, no further negotiation is possible.

How it's Used

Let's suppose, like in the previous example, that a browser is configured to work with a SPDY Proxy that, in turn, is accessing a SPDY-enabled origin... this is the best case scenario... Let's also assume that we're using a very simple key negotiation strategy where the browser and proxy have a shared secret key, and the browser and origin have a shared secret key.

Client                  Proxy                 Origin
  |                       |                     |
  |---------------------->|                     |
  | 1) SYN                |                     |
  |<----------------------|                     |
  | 2) SYN_ACK            |                     |
  |---------------------->|                     |
  | 3) ACK                |                     |
  |                       |                     |
  |---------------------->|                     |
  | 4) KEY_NEGO           |                     |
  |  key_id=1             |                     |
  |  alg=1                |                     |
  |  key="mykey"          |                     |
  |---------------------->|                     |
  | 6) SYN_STREAM         |                     |
  |  stream_id=1          |                     |
  |  key_id=1             |                     |
  |  method = CONNECT     |                     |
  |  host = origin        |                     |
  |                       |-------------------->|
  |                       | 7) SYN              |
  |                       |<--------------------|
  |                       | 8) SYN_ACK          |
  |                       |-------------------->|
  |                       | 9) ACK              |
  |<----------------------|                     |
  | 7) SYN_REPLY          |                     |
  |  stream_id=1          |                     |
  |  key_id=1             |                     |
  |  status=200           |                     |
  |                       |                     |
  |-------------------------------------------->|
  | 8) KEY_NEGO                                 |
  |  key_id=1                                   |
  |  alg=1                                      |
  |  key="otherkey"                             |
  |-------------------------------------------->|
  | 9) SYN_STREAM                               |
  |  stream_id=1                                |
  |  key_id=1                                   |
  |  method= GET                                |
  |  host=origin                                |
  |<--------------------------------------------|
  | 10) SYN_REPLY                               |
  |  stream_id=1                                |
  |  status=200                                 |
  |                                             |

With this flow, when the browser connects to the proxy, prior to sending the CONNECT method to ask the proxy to connect with the remote origin, it sends a KEY_NEGO frame to the proxy telling it that it wishes to associate the KEY_ID=1 with the shared secret key "mykey". Then, the browser sends a SYN_STREAM that references KEY_ID=1 to the proxy. The headers in this frame, and all subsequent frames in the stream would be encrypted using the selected secret. Because this secret is only shared by the browser and the proxy, the communication is safe, even tho the connection itself is not. 

The proxy then establishes a connection to the origin and sends an appropriate SYN_REPLY to the browser. At this point, a tunnel is established with the origin and the proxy is effectively acting like a dumb router, just forwarding packets along.

At this point, the browser sends a new KEY_NEGO to the origin server, telling the origin to associate KEY_ID with the shared secret labeled "otherkey". The browser then sends a SYN_STREAM referencing that key to the origin. The headers in this frame, and all subsequent frames in the stream would be encrypted using the selected secret. Because this secret is only shared by the browser and the origin, the communication is safe, even tho the connections between the browser and the proxy, and the proxy and the origin are not secured. 

Obviously, there would be a number of specific details to work out in terms of the specific construction of the data in the KEY_NEGO frames in order to prevent spoofing, etc, but that should be fairly straightforward to work out. 

Consider an alternate situation... it is often the case that a user will access a website using plain ole cleartext HTTP. When the user goes to login, however, the site SHOULD redirect the user to a TLS connection. This process tends to be extremely error prone and inefficient, at best. Using In-Stream Key Negotiation, we can streamline the process, and dynamically upgrade the security of a session without requiring the TCP connection to be torn down and reestablished. Further, the upgrade can be prompted by the server.

For example...

Client                  Proxy                 Origin
  |                       |                     |
  |---------------------->|                     |
  | 1) SYN                |                     |
  |<----------------------|                     |
  | 2) SYN_ACK            |                     |
  |---------------------->|                     |
  | 3) ACK                |                     |
  |                       |                     |
  |---------------------->|                     |
  | 4) SYN_STREAM         |                     |
  |  stream_id=1          |                     |
  |  method = CONNECT     |                     |
  |  host = origin        |                     |
  |                       |-------------------->|
  |                       | 5) SYN              |
  |                       |<--------------------|
  |                       | 6) SYN_ACK          |
  |                       |-------------------->|
  |                       | 7) ACK              |
  |<----------------------|                     |
  | 8) SYN_REPLY          |                     |
  |  stream_id=1          |                     |
  |  status=200           |                     |
  |                       |                     |
  |-------------------------------------------->|
  | 9) SYN_STREAM                               |
  |  stream_id=1                                |
  |  method= GET                                |
  |  host=origin                                |
  |<--------------------------------------------|
  | 10) KEY_NEGO                                |
  |  key_id=2                                   |
  |  alg_id=1                                   |
  |  key = ...                                  |
  |<--------------------------------------------|
  | 10) SYN_REPLY                               |
  |  stream_id=1                                |
  |  status=200                                 |
  |  key_id=1                                   |
  |                                             |

Here, the server, seeing that the communication from the browser has not been secured yet, can initiate a key negotiation dynamically within the same session, choosing to encrypt the response to the client. 

There are a range of possible benefits to this approach...

1. The ability to selectively encrypt individual streams using multiple keys over a single TCP connection. For example, suppose a origin serves multiple hosts, and the browser wishes each of those hosts. KEY_NEGO could be potentially be used to establish separate encryption keys for each host over a single connection. Meaning that only the target host would be capable of seeing the data intended specific meant for it.

2. The ability to renegotiate keys on the fly, without tearing down and reestablishing the connection. 

3. In-stream negotiated keys can be used for mutual authentication. For instance, suppose the browser user has a key pair and an associated user ID; the origin knows the user ID and the public key. Likewise, the user knows the origins public Key. When sending the KEY_NEGO to the origin, the browser would include the User ID and a nonce, both signed by the private key. In response, the server sends a KEY_NEGO with it's identifier and a nonce signed by it's private key.  Each can then use the public key of the other party to encrypt their respective halves of the stream...

Client                                        Origin
  |<--------------------------------------------|
  | 1) KEY_NEGO                                 |
  |  key_id=1                                   |
  |  alg_id=1                                   |
  |  key = ...                                  |
  |-------------------------------------------->|
  | 2) SYN_STREAM                               |
  |  stream_id=1                                |
  |  method= GET                                |
  |  host=origin                                |
  |<--------------------------------------------|
  | 10) KEY_NEGO                                |
  |  key_id=2                                   |
  |  alg_id=1                                   |
  |  key = ...                                  |
  |<--------------------------------------------|
  | 10) SYN_REPLY                               |
  |  stream_id=1                                |
  |  status=200                                 |
  |  key_id=2                                   |
  |                                             |

Assuming the public keys are trustworthy (which is a different problem altogether) we effectively achieve a basic level of mutual authentication that is at least better than typical HTTP Basic Auth.  (yes, there are a range of issues that would need to be addressed on bootstrapping this, but that's a separate problem)

4. Clear and Encrypted traffic on a single connection. This, obviously would require client and senders to take care when opening new streams but allows applications to pick and choose which pieces are actually necessary to secure and which parts are ok to transfer in the clear. For instance, there are known obvious problems when a website publishes pages that allow for a mix of secure and insecure connections. Note that these problems are NOT a protocol error, but an application error -- that is, browsers and web applications have a very bad habit of doing stupid things by default -- such as taking cookies returned in an HTTPS response and sending them back unencrypted in a followon HTTP request. That's just plain silly. There is a faction that believes that the cure for these kinds of problems is to simply require TLS across the board. The better solution, however, is for browsers to become more intelligent about how they're making use of things like Cookies.

For now, that's it. Time for me to head to the gym for bit. I'll try to post more on this tomorrow, exploring the various other options such as proxying a secure context to non-SPDY origins...

No comments:

Post a Comment