[TLS] Refactoring the negotiation syntax

Eric Rescorla <ekr@rtfm.com> Wed, 13 July 2016 17:13 UTC

Return-Path: <ekr@rtfm.com>
X-Original-To: tls@ietfa.amsl.com
Delivered-To: tls@ietfa.amsl.com
Received: from localhost (localhost []) by ietfa.amsl.com (Postfix) with ESMTP id 0815812D13D for <tls@ietfa.amsl.com>; Wed, 13 Jul 2016 10:13:37 -0700 (PDT)
X-Virus-Scanned: amavisd-new at amsl.com
X-Spam-Flag: NO
X-Spam-Score: -2.599
X-Spam-Status: No, score=-2.599 tagged_above=-999 required=5 tests=[BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, HTML_MESSAGE=0.001, RCVD_IN_DNSWL_LOW=-0.7] autolearn=ham autolearn_force=no
Authentication-Results: ietfa.amsl.com (amavisd-new); dkim=pass (2048-bit key) header.d=rtfm-com.20150623.gappssmtp.com
Received: from mail.ietf.org ([]) by localhost (ietfa.amsl.com []) (amavisd-new, port 10024) with ESMTP id 85j_BfrJsXq7 for <tls@ietfa.amsl.com>; Wed, 13 Jul 2016 10:13:35 -0700 (PDT)
Received: from mail-yw0-x22a.google.com (mail-yw0-x22a.google.com [IPv6:2607:f8b0:4002:c05::22a]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by ietfa.amsl.com (Postfix) with ESMTPS id BF6DF12D08E for <tls@ietf.org>; Wed, 13 Jul 2016 10:13:34 -0700 (PDT)
Received: by mail-yw0-x22a.google.com with SMTP id w127so49755763ywf.3 for <tls@ietf.org>; Wed, 13 Jul 2016 10:13:34 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=rtfm-com.20150623.gappssmtp.com; s=20150623; h=mime-version:from:date:message-id:subject:to; bh=0TyEHUm87L5b4MezPw3A61CRFvVrh7jO1+kQB4r8oCM=; b=x5nLP68odybXvp7FmBORTbMUnHI1zMCytvvK4DcX+H+We1Z7Pm/iLzxT+wNi91VK9b HRFpCb6+fueHcstdbvPfjobaUckBLqk3IpW5L+w8HybhcOZ5se3X2TCjNKIrLZwLeu8/ DCHCWyPP1VC5Y8ochEyrFkTIPUIM4WHFgGjUYAw5Re/O9V7wcR5rREz17wxO47XFHZUg g1kwX/cQrtz5jjF/cc+9LfNCmbmugBAWnw7OIuBIQGcQlb/lCdChw+3Hapja13RueoaX RnO2oxfErkqxtTopljUTm+DWa9tkXsHU8jmkHvbmnIeHwggjdfB956I7WBba4Z9erb33 whmg==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:mime-version:from:date:message-id:subject:to; bh=0TyEHUm87L5b4MezPw3A61CRFvVrh7jO1+kQB4r8oCM=; b=XAkQwAWbZ9W0UsHctEE/wqrvVH54TVBVtUY050PDBcptrSmX6mOc8D9gKtrRHPu83m r6PDKOF3+8IDp0oqwSuaQBFhiB3Z+567wMAqX+4E0JTYwiwLlP6k83iOI1oONSuBawb9 it0i1MNFtkxdftnUJy+wkhRrSnDQs53s+a73NmVSyEssXafFu7SCHEGUG6ywvLbrbkAD LSXvloo11Yub0wkeDR2oQmQfBq+nZ+Gu675NXh19E+fcXJ/vCcea0MlrrPxSQIN4kpXn 6B6St6HnFkR6Rgy9IaV7KYeGcJJnym8BTAqj0l0NGrUPiXJ7kbIXLppy7JcU9dt7m3Ba qndg==
X-Gm-Message-State: ALyK8tLo/i7Ys4rXmFRIMXSuopsScxAoYJzh2o41dOxewN6JjfrbwNg48HZ3ELdsLoKEe/22zh3FMihLy8d5bw==
X-Received: by with SMTP id l128mr7039489ywd.93.1468430013696; Wed, 13 Jul 2016 10:13:33 -0700 (PDT)
MIME-Version: 1.0
Received: by with HTTP; Wed, 13 Jul 2016 10:12:54 -0700 (PDT)
From: Eric Rescorla <ekr@rtfm.com>
Date: Wed, 13 Jul 2016 10:12:54 -0700
Message-ID: <CABcZeBPh+BGtnBb725G+YzZZzdSUh5KtViqh3Z339apSKRpygg@mail.gmail.com>
To: "tls@ietf.org" <tls@ietf.org>
Content-Type: multipart/alternative; boundary=001a114e630e5839b20537878167
Archived-At: <https://mailarchive.ietf.org/arch/msg/tls/CCcF8rcu3qa_Mxj_VKdIEF8d0UA>
Subject: [TLS] Refactoring the negotiation syntax
X-BeenThere: tls@ietf.org
X-Mailman-Version: 2.1.17
Precedence: list
List-Id: "This is the mailing list for the Transport Layer Security working group of the IETF." <tls.ietf.org>
List-Unsubscribe: <https://www.ietf.org/mailman/options/tls>, <mailto:tls-request@ietf.org?subject=unsubscribe>
List-Archive: <https://mailarchive.ietf.org/arch/browse/tls/>
List-Post: <mailto:tls@ietf.org>
List-Help: <mailto:tls-request@ietf.org?subject=help>
List-Subscribe: <https://www.ietf.org/mailman/listinfo/tls>, <mailto:tls-request@ietf.org?subject=subscribe>
X-List-Received-Date: Wed, 13 Jul 2016 17:13:37 -0000

Hi folks,

There have been a lot of discussions about whether we should try to
refactor cipher-suite negotiation to be less monolithic in the cipher
suite. I've generally been on the "no" side of that on cost/benefit
grounds as well as not quite seeing how it would fit into the rest
of the infrastructure. However, now that we're starting to get full
implementations, and it's becoming clearer how the new elements
(principally PSK-resumption and key_shares) interact with cipher
suites, I've started to think that in fact we may be able to clean
things up. One proposal for this is below [0]. I know this is
pretty late in the process, but if we do want this change it's
better to do it now.

I'm sure we'll need to discuss this in Berlin, but in the meantime,
fire away.

The basic idea here is to factor out the TLS 1.3 negotiation into three
mostly orthogonal axes.

- Symmetric cipher/PRF  -- indicated by the cipher suite list as in TLS 1.2
- Key exchange -- indicated by the key_shares and pre_shared_key extensions
- Authentication -- indicated the signature_algorithms and pre_shared_key

A proposal for how to do this is below. See the end for other options,
caveats, etc.

If we take PSK out of the picture, this gives us a very simple structure:

- The client offers a set of key_shares and the server picks one that it
  likes [1].
- The client offers a list of signature_algorithms and the server picks
  a certificate/key that matches that list and signs with it.

In other words, we just eliminate the redundancy with the cipher suite
indications. This leaves is with the question of how to handle the
existing TLS 1.2 cipher suites. We can either assign new cipher suites
or say that any cipher suite with *_aead_alg_hash means that we
support aead_alg_hash. Matter of taste.

PSK is handled by extending the concept of PSK flags that we already
have in NewSessionTicket to also include uses of PSKs where you
indicate the way in which you are using the PSK (or want it to be
used). There a bunch of ways to encode this. I'll give you the one I
mostly prefer below and then a one that's a smaller change but I think
a little less elegant at the end [note #4].

First, we replace the flags word of the different KE modes for the
ticket with lists of code points, as below:

   enum {
     psk_ke(0),          // PSK key exchange
     psk_dhe_ke(1),      // PSK + DHE key exchange
   } PskKeModes;

And then add new code points for being able to use the PSK with and
without signatures (from the server). We had pretty rough consensus in
B-A that we needed this mode and [draft-thomson-tls-0rtt-and-certs-01]
is part of the motivation for this idea.

   enum {
     psk_auth(0),        // PSK only
     psk_sign_auth(1),   // PSK + a signature (as in draft-thomson) [5]
   } PskAuthModes;

This gives us the following NewSessionTicket message where we have
replaced the flags word with two (potentially ordered) lists:

   struct {
       uint32 ticket_lifetime;
       PskAuthModes auth_modes<1..255>;
       PskKeModes ke_modes<1..255>;
       TicketExtension extensions<2..2^16-2>;
       opaque ticket<0..2^16-1>;
   } NewSessionTicket;

We now need a way to indicate that the server will do 0-RTT (previously
in the flags word) so we add a new 0-RTT extension:

   struct {
       uint32 ticket_age_add;
   } TicketEarlyDataInfo;

This has two nice properties:

- You don't need to provide ticket_age_add when you don't do 0-RTT
  (and it is silly if you don't).
- It exercises this extension mechanism, which is good for future-proofing.

PreSharedKeyExtension becomes:

    struct {
       PskAuthMode auth_modes<1..255>;
       PskKeModee ke_modes<1..255>;
       opaque identity<0..2^16-1>;
    } PskIdentity;

    struct {
         select (Role) {
             case client:
                 PskIdentity identities<2..2^16-1>;
              case server:
                 PskAuthMode auth_mode;
                 PskKeMode ke_mode;
                 uint16 selected_identity;
     } PreSharedKeyExtension;

The way to interpret these is as follows:

- With each identity, the client indicates to the server which modes
  it can be used with.
- When the server responds with an identity, it tells you how it
  used it.

You might ask why you need the server to indicate what it did.  The
reason is that we would like the client to know in advance (at the
time of the ServerHello) whether the server has sent
Certificate/CertificateVerify rather than having to figure it out from
what messages the server sends. The ke_mode field is redundant
(because you also infer it from the server key_shares) but I added it
for parity.

I haven't implemented this yet (am going to try to take a crack at that
before Berlin) but I believe based on experience with the NSS
negotiation that it will be simpler. I know it removes a bunch of odd
edge cases which have accumulated over the years, but maybe it adds


0. I've discussed this with and/or borrowed ideas from Richard Barnes,
David Benjamin, Karthik Bhargavan, Dave Garrett, Nick Sullivan, Martin
Thomson, and others. Thanks to those guys, but blame me if you think
this is bad.

1. The other major encoding option here is to have dummy
group/signature_algorithm indicators that tell you whether you can use
a pure PSK. When I and others discussed this, the consensus was that
that was less clean.

2. One question that comes up at the same time is whether we should
allow multiple key shares to be used, which is a structure that this
makes pretty easy. Basically, the server would just supply as many
counter-shares as it wanted and then we'd need a defined order for how
they were inserted into the key schedule. This feature would be nice
for enabling post-quantum, but probably better to just define
<PQ-Algorithm + Curve> code points.

3. Note that because of this PSK-PRF interaction, PSK isn't totally
orthogonal with AEAD/PRF. I.e., you cannot use the same PSK with
HKDF-SHA256 and HKDF-SHA384 if you want to be on the cryptographic
fairway, but I think it should be easy enough to filter out the cipher
suites to make that work.

4. The other major alternative is just to use the flags bits. So we
would extend and generalize the flags as shown below.

   enum {
     psk_auth(8),         // New (no server cert)
     sig_psk_auth(16)     // New (sign with server cert)
   } PskUsageFlags;

These new flags have the expected meaning, namely:

psk_auth         The server will do connections with just
                 PSK authentication (equivalent to PSK now)

sig_psk_auth     The server will do connections with PSK
                 plus signature (not currently specified).

Then we update the PreSharedKeyExtension as follows:

    struct {
          uint32 usage_flags;
          opaque identity<0..2^16-1>;
    } PskIdentity;

    struct {
         select (Role) {
             case client:
                 PskIdentity identities<2..2^16-1>;
              case server:
                 uint32 usage_flags;  // New (one from each category)
                 uint16 selected_identity;
     } PreSharedKeyExtension;

The idea here is:

- The client indicates how each PSK can be used (by flags, which
  need to be a subset of the ticket flags).
- The server can pick a PSK and indicate how it was actually

For instance, if the client wants to do PSK w/ ECDHE it would indicate
dhe_psk_ke. If it only wants PSK it would indicate psk_ke. If it was
willing to do both, it would indicate the bitwise OR.

Similarly, if the client wants PSK for auth, it would indicate
psk_auth. If it wants the server to sign, it indicates sig_psk_auth,
and so on (indicating neither is silly because it makes the key

By contrast, the server just sets the bits that it actually used.
I.e., if it sets psk_auth, that means it won't be signing and
there is no Certificate/CertificateVerify. OTOH if it sets
sign_psk_auth, that means it will be signing and there will
be a Certificate/CertificateVerify.

This leaves early data. As above, the client uses the early_data
bit to indicate that the PSK was used to encrypt early data
(obviously, this can just be one key) and the server uses it to
indicate that it accepted early data. ticket_age needs to go in
its own extension, but that's easy enough.

5. Note: this would allow for modes where the server signs over a PSK
handshake with no DHE at all. The resumption_ctx mechanism is intended
to ensure that that is OK, but we'd need to confirm with analysis.