Subject: KEY_NEGO within SPDY (was: Re: Response to HTTP2 expresions of interest)

From: James M Snell <[email-masked]>
Date: Fri, 13 Jul 2012 14:19:17 -0700

This idea was born in a separate thread, wanted to pull it out here so
as not to sidetrack the other discussion too much and to get the idea
out for discussion. This is, more or less, a data dump of sorts. May
be a bit disorganized but, again, the idea is just to get things
rolling for discussion purposes.

Side note: I am currently engaged in this discussion as an individual
and the ideas expressed here should be considered to be based on my
own personal thoughts and opinions and not those of my employer. As
yet, my employer has not yet officially weighed in the discussions
around HTTP/2.0.

Basic Premise: The revised framing model used with SPDY provides us
with an opportunity to provide support for stream-level protection of
data without requiring TLS. Doing so helps routers and intermediaries
do their job more efficiently while protecting the confidentiality of
data in transit. The proposal below introduces the concept of a new
KEY_NEGO frame for SPDY and proposes modified Data and Headers Frame
formats to support in-stream, per-frame encryption.

KEY_NEGO Control Frame

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

KEY_NEGO Frames are used to negotiate keys for use within a SPDY session.

The ID is an 8-bit identifier uniquely identifying the negotiated key
within the session.

KEY_NEGO Frames initiated by the server must have an even ID
KEY_NEGO Frames initiated by the client must have an odd ID

If client receives a KEY_NEGO Frame containing an odd ID that it did
not initiate, it must ignore the KEY_NEGO frame

Likewise, if server receives a KEY_NEGO Frame containing an even ID
that it did not  initiate, it must ignore the KEY_NEGO frame

The ID does NOT recycle... once the range of key IDs available for a
given connection have been exhausted, no further key_negotiation is
possible.

It is possible for a particular Key ID to be renegotiated...

The ALG_ID is an unsigned 16-bit identifier specifying the specific
key negotiation algorithm in use. If the value is 0x1, then an
extension algorithm is being used (see below). All other values > 0x1
shall identify specifically registered key negotiation algorithms. The
structure of the Data field depends entirely on the ALG_ID.

If ALG_ID == 0x1, then the structure of Data is:

 +-----------------------------+
 | Name Length (int32) | Name  |
 +-----------------------------+
 |            Data             |
 +-----------------------------+

Where the Name Length is the length, in octets of the Name field. The
Name field specifies the name of key negotiation algorithm. The
Remaining Data field contains the detail specific to the type of key
negotiation. This approach allows us to optimize for the most common
cases while supporting extension mechanisms as well.

Once the Key Negotiation is complete, the ID of the KEY_NEGO frame
becomes the identifier for the Key whenever used within the SPDY
session.

If Key Negotiation involves multiple steps, each subsequent KEY_NEGO
frame will use the same ID. The specific number of KEY_NEGO frames
necessary to negotiate a single Key depends on the algorithm used.

 For each data or control frame, the flag 0x4 indicates that the frame
is encrypted. An additional, optional KEY_ID field would need to be
added to each frame that supports encryption. Candidates are:

 Encryption-Supported Data Frame:

   +----------------------------------+
   |C|       Stream-ID (31bits)       |
   +----------------------------------+
   | Flags (8) | KEY_ID | Length (24) |
   +----------------------------------+
   |               Data               |
   +----------------------------------+

 Encryption-Supported Headers Frame:

   +------------------------------------+
   |1|   version     |          8       |
   +------------------------------------+
   | Flags (8)  |  KEY_ID | Length (24) |
   +------------------------------------+
   |X|          Stream-ID (31bits)      |
   +------------------------------------+
   | Number of Name/Value pairs (int32) |   <+
   +------------------------------------+    |
   |     Length of name (int32)         |    | This section is the "Name/Value
   +------------------------------------+    | Header Block", and is compressed.
   |           Name (string)            |    |
   +------------------------------------+    |
   |     Length of value  (int32)       |    |
   +------------------------------------+    |
   |          Value   (string)          |    |
   +------------------------------------+    |
   |           (repeats)                |   <+

The KEY_ID is the 8-bit identifier of the key previously negotiated
using a KEY_NEGO frame.

If a frame uses a KEY_ID that has not yet been negotiated fully,
decryption of the frame becomes impossible and the receiver can reject
it.. possibly by closing the stream.

Example: Assume a preshared key is being used, identified using the
name "ourkey", assume ALG_ID=2 means "pre-shared key", where Data is
the US-ASCII encoded name of the key.

[Connection is opened]
[Client sends a KEY_NEGO with ID=1,ALG_ID=2,Data="ourkey"]
[Client sends SYN_STREAM to open the stream]
[Client sends Encrypted Data Frame referencing KEY_ID=1]
[Server sends Encrypted Data Frame referencing KEY_ID=1]
[Connection is closed]

Any intermediary in the flow can participate in the Key Negotiation.
For example, suppose client A opens a connection with Server C via
intermediary B. Assume that A and B have a preshared key

[A opens a connection with B]
[A sends a KEY_NEGO with ID=1, ALG_ID=2,Data="ourkey"]
[A sends SYN_STREAM to open the stream]
[B routes SYN_STREAM to C]
[A sends encrypted header and data frames to B referencing KEY_ID=1]
[B decrypts the frames, and forwards the frames in clear text to C]

This provides us with the same basic benefits as TLS termination at
intermediaries. One key question that arises is how B would know that
it should decrypt the frame and send it to C in plain text rather than
simply forwarding it on in an encrypted state. What I imagine is that
would largely just be a matter of configuration for each route.

There are a variety of additional advantages to this approach...

1. When a user-agent requires the ability to communicate with two
hosts at the same location, and chooses to do so using a single
connection, it is possible for the user-agent to negotiate a unique
key for each host while still interleaving frames for each host within
the same multiplexed connection.

Example:
[Connection is opened]
[Client sends KEY_NEGO with ID=1, ALG_ID=2,Data="host_1_key"]
[Client sends KEY_NEGO with ID=2, ALG_ID=2,Data="host_2_key"]
[Client sends SYN_STREAM to open the stream with Host_1]
[Client sends SYN_STREAM to open the stream with Host_2]
[Client sends encrypted header and data frames to Host_1 referencing KEY_ID=1]
[Client sends encrypted header and data frames to Host_2 referencing KEY_ID=2]
[Host_1 sends encrypted header and data frames to Client referencing KEY_ID=1]
[Host_2 sends encrypted header and data frames to Client referencing KEY_ID=2]
[Connection is Closed]

2. Encrypted-content over Unsecured Transport... For example: a user
is accessing his bank account using their mobile device. The
user-agent opens a single unsecured connection to the banks server,
over which requests for unsecured static resources are retrieved. The
user needs to securely log in to their account. Using KEY_NEGO, the
user-agent can negotiate a secured stream using the same connection,
passing the encrypted authentication credentials along without the
need to set up an expensive, separate TLS-secured connection.

Example:
[Connection is opened]
[Client sends SYN_STREAM to open Stream_1]
[Client sends clear-text header and data frames over Stream_1]
[Client sends KEY_NEGO with ID=1, ALG_ID=2,Data="ourkey"]
[Client sends SYN_STREAM to open Stream_2]
[Client sends clear-text header and data frames over Stream_1]
[Client sends encrypted header and data frames over Stream_2
referencing KEY_ID=1]
[Connection is Closed]

3. Keys can be renegotiated on the fly without tearing down and
reestablishing the connection or requiring a new connection to be
established. For example, suppose a server or client determines that a
particular type of request requires a greater level of encryption than
what was originally established for the stream. The intermediary can
attempt to renegotiate the key or negotiate a new key to handle the
new requirements.

Example:
[Connection is opened]
[Client sends SYN_STREAM to open Stream_1]
[Client sends clear-text header and data frames over Stream_1]
[Server sends KEY_NEGO with ID=1, ALG_ID=2,Data="ourkey"]
[Server sends clear-text header and data frames over Stream_1 to client]
[Connection is Closed]

4. Depending on the specific details of the Key_Nego algorithm used,
Key negotiation frames can be interleaved with data and control
frames. For instance, suppose that a particular Key_nego algorithm
requires multiple steps... to use a simple example, let's just say
we're using DH for the key_nego, the sender can perform their half of
the key_nego and prepare an encrypted data frame... sending it along
with the KEY_NEGO for the receiver to process.

Example: (Assume ALG_ID=3 refers to something like DH Key nego)
[Connection is opened]
[Client sends SYN_STREAM to open Stream]
[Client sends KEY_NEGO with ID=1, ALG_ID=3,Data={key nego params}]
[Client sends encrypted header and data frames referencing KEY_ID=1]
[Server sends KEY_NEGO with ID=1, ALG_ID=3,Data={key nego response params}]
[Server sends encrypted header and data frames referencing KEY_ID=1]
[Connection is Closed]

5. Multiple keys with different strengths can be negotiated within a
single connection, allowing for higher-priority frames to use stronger
protection than lower-priority frames.

Example: For example, suppose that the second Data frame contains
financial transaction information that requires higher security
[Connection is opened]
[Client sends a KEY_NEGO with ID=1,ALG_ID=2,Data="weakkey"]
[Client sends a KEY_NEGO with ID=2,ALG_ID=2,Data="strongkey"]
[Client sends SYN_STREAM to open the stream]
[Client sends Encrypted Data Frame referencing KEY_ID=1]
[Client sends Encrypted Data Frame referencing KEY_ID=2]
[Connection is closed]

6. With this approach, for many scenarios, use of TLS/SSL could
potentially be eliminated altogether.

As always, thoughts/opinions/gripes/praise are welcome...

- James

---

From: James M Snell <[email-masked]>
Date: Fri, 13 Jul 2012 14:31:52 -0700

My apologies the example for Item #3 should have been....

 Example:
 [Connection is opened]
 [Client sends SYN_STREAM to open the stream]
 [Client sends clear-text header and data frames1]
 [Server sends KEY_NEGO with ID=1, ALG_ID=2,Data="ourkey"]
 [Server sends encrypted header and data frames to client referencing KEY_ID=1]
 [Connection is Closed]

- James