[Noise] Resumable pipes

Trevor Perrin trevp at trevp.net
Sun Jun 29 10:41:04 PDT 2014


There's some interest in adding a "resumable pipe" notion.  This would
allow 0-RTT resumption (think QUIC, or the abandoned Snap Start for
TLS).

This would be significantly more complex than existing pipes.
Nonetheless, I think it could be done in a pretty nice way, building
off what we've got.

Below is a sketch, feedback appreciated:


Resumable Pipes
===========

Overview
---
After a regular 1-RTT connection, the client might want subsequent
connections to be "0-RTT", so it can send data in the first message.

It would be possible to do a simple symmetric-key-only resumption
(like TLS).  But a long time could elapse between the initial and
resumed connections.  So we'd like to add robustness against client
key compromise.

To do this, the server will have a "resumption public key" that it can
replace at whatever frequency it wants.  The client's resumption
request will use a Noise box with two client ECDH keys:
 - an ephemeral key, which means the resumed session will be
confidential and have server-authentication even if the resumption
secret and client's identity key leaks
 - the client's identity key, which means the resumed session will
have client-authentication if only the resumption secret leaks (e.g.
the identity key is on separate HW)

This isn't as good as a full handshake using the server's identity key
and a fresh server ephemeral, but it seems like a good balance between
security and efficiency.

In addition to the "resumption public key", clients and servers will
also store a one-time-use "resumption secret", derived from the
specific session they are resuming.  Upon successful resumption the
server will delete this secret, which also prevents replay attack on
the resumption.

Finally, we'd also like it to be impossible for an observer to
distinguish the type of handshake (initial / successful resumption /
failed resumption), and for any tampering with the handshake - even
with "junk" padding bytes - to trigger an error.


Message sequences (high-level; assumes client sends AppData first):
---
Initial handshake
-> ClientEph
<- Box(ServerHello)
-> Box(ClientHello) || Body(AppData)

Successful resumption
-> Box(ClientResumeHello) || Body(PresumptiveAppData)
<- Body(ServerResumeHello || AppData)
-> Body(AppData)

Failed resumption
-> Box(ClientResumeHello) || Body(PresumptiveAppData)
<- Box(ServerHello)
-> Box(ClientHello) || Body(ResendAppData)


Tags
---
The server must distinguish the ClientEph and ClientResumeHello
messages.  The client must distinguish the ServerHello and
ServerResumeHello.  To signal these without leaking information, two
"tags" are used:

Client tag : 16-byte random for ClientEph.  For ClientResumeHello, a
one-time value that indicates the resumption public key (for example:
8 byte nonce, 8 byte MAC(resumption_private_key, nonce)).

Server tag : 16-byte random for ServerHello.  For ServerResumeHello, a
16-byte value derived from the resumption keys.


Padded message sequences
---
Each of the 3 messages (client #1, server #1, client #2) must be
padded to a constant length.  The data following the tags may be
recognizable as an EC public key; all other message contents appear
random.

To prevent tampering of the JunkRandom, JunkPubkey, and tag fields,
the SHA512 of all previous messages (HandshakeHash) is either (a)
truncated and used as the CV for ServerHello, or (b) used as an
authenticated-data header for Body(ServerResumeHello).

Initial handshake
-> RandomTag || ClientEph || JunkRandom
<- RandomTag || Box(ServerHello)
-> Box(ClientHello) || Body(AppData)   [ephemeral pubkey omitted from box]

Successful resumption
-> ClientTag || Box(ClientResumeHello) || Body(PresumptiveAppData)
<- ServerTag || JunkPubKey || Body(ServerResumeHello || AppData)
-> Body(AppData)

Failed resumption
-> ClientTag || Box(ClientResumeHello) || Body(PresumptiveAppData)
<- RandomTag || Box(ServerHello)
-> Box(ClientHello) || Body(ResendAppData)   [ephemeral pubkey omitted from box]


Messages
---
The ServerHello, ClientHello and ResumeHello messages are all
contained in Noise boxes.  The ServerResumeHello and AppData messages
are not.

The ServerHello box and ServerResumeHello body contain as their
initial plaintext bytes:

struct {
    bytes client_tag;
    bytes resumption_pubkey[DH_LEN];
} ResumptionData;

If the server doesn't wish to allow this connection to be resumed, it
MUST set client_tag to all zeros.

The ClientHello box contains as its initial plaintext a single byte
(1) to accept storing offered resumption data, (0) otherwise.

The ClientResumeHello box contains as its initial plaintext a single
byte (1) to request new resumption data, (0) otherwise.

The contents of the boxes are otherwise unspecified.  However, note
that the ClientResumeHello MUST contain an indication of the client's
identity, so the server can lookup the resumption secret.

The boxes use the following keys and kdf numbers:

C,C' : Client identity and ephemeral keys
S,S' : Server identity and ephemeral keys
R : resumption key

ServerHello (S',S -> C', kdf_num = 8)
ClientHello (C',C -> S', kdf_num = 10)
ClientResumeHello (C',C -> R, kdf_num = 12)


Key derivation
---
For an Initial handshake or failed resumption handshake, key
derivation after ServerHello is similar to non-resumable pipes, except
that the resumption secret is also derived:

(C,c)   : client's public key C and private key c
(S,s)   : server's public key S and private key s
(C',c') : client's ephemeral public key C' and private key c'
(S',s') : server's ephemeral public key S' and private key s'

RS_LEN = 32

cc_client, cc_server, resumption_secret = KDF(cv_h2, zeros[CV_LEN],
SUITE_NAME || 14, CC_LEN * 2 + RS_LEN)

For a successful resumption, key derivation is performed after
ClientResumeHello, but takes a resumption_secret as input and also
derives a ServerTag:

TAG_LEN = 16

cc_client, cc_server, resumption_secret, server_tag = KDF(cv_h1,
resumption_secret, SUITE_NAME || 16, CC_LEN * 2 + RS_LEN + TAG_LEN)


Trevor


More information about the noise mailing list