Subject: [TLS] proposal to encrypt ContentType for TLS 1.3

From: Daniel Kahn Gillmor <[email-masked]>
Date: Tue, 01 Jul 2014 18:09:41 -0400

hey folks--

i just opened a pull request to propose that the TLS ContentType (when
actually using a proper cipher) should itself be encrypted, rather than
in the clear:

 https://github.com/tlswg/tls13-spec/pull/51

As the main commit in this request says:

>     Move Content Type inside encryption
> 
>     Previous versions of TLS have left the Content Type in the clear
>     without justifying the exposure of this piece of information.
>     
>     This change moves the Content Type inside the encryption to protect
>     its confidentiality.
>     
>     For backward compatibility during the initial handshake negotiation,
>     we need to keep the type field at its original location on the wire
>     for cleartext packets.  This means redefining the NULL_NULL case to
>     not simply be an pseudo-cipher that implements the identity
>     transformation, but to explicitly state it as sending the TLSPlaintext
>     struct directly over the wire.

I'd be interested in feedback on this suggestion.

Having the content-type encrypted would mean that TLS alerts,
handshakes, and application data would be indistinguishable from one
another to an observer, once a ChangeCipherSpec had taken place.

This also opens the way to consider the creation of other content types
(e.g. "padded application data") without having to decide whether the
exposure of a novel ContentType to an observer is itself a risk.

If this change is made, it's also relatively easy to just drop the TLS
version field for each encrypted TLS record layer fragment.

All the best,

	--dkg

---

From: Martin Thomson <[email-masked]>
Date: Tue, 1 Jul 2014 15:31:24 -0700

> version field for each encrypted TLS record layer fragment.

I'd like to see that happen.

I think that the bit that causes me a little concern is that content
type shifts around, with a special case.  If you have a special case
like that, then there isn't much of a difference between moving it
while under encryption to moving it always (other than the need to
keep ClientHello clean.  But that doesn't seem like the right answer
either.

It's more disruptive, but if the definition of the AEAD cipher were to
encompass the entire struct, then you could define NULL_NULL in a way
that placed content type in the old location.  I don't know if that is
any better, though it would probably be the way I'd structure software
that processed the record layer.

Then you have a different structure in place of TLSCipherText on which
the construction depends on the cipher.  NULL_NULL can define
something that looks like legacy TLS: ContentType, Version,
Payload.length, Payload.data.  Ciphersuite constructions can define
something like this: Payload.length, Nonce, Encrypted { ContentType,
Payload.data }; with variance discouraged, but possible.

---

From: Eric Rescorla <[email-masked]>
Date: Fri, 4 Jul 2014 17:11:55 -0700

> version field for each encrypted TLS record layer fragment.

This seems like an idea with a fair bit of merit, but if I understand
correctly, it's going to result in a stream of data which cannot be
parsed into TLS records by existing network elements.
I'm concerned about how this is going to interact with various
elements which process the ciphertext. For instance:

- Protocol enforcement devices which expect to see a stream of records.
- RFC 5764 elements which multiplex STUN, SRTP, and DTLS and
  use the first byte for this purpose.

I think we can fix the second category by requiring explicit opt-in for
this feature, either by an extension or by it being TLS 1.3 (though
we would still need to define how to demux), but the first category
seems potentially more problematic. It seems like it might be worth
doing some experiments to see what the impact on success rates
is. [0]

-Ekr

[0] If this turns out to be a problem, there is an ugly but workable
solution, which is to have all TLSCiphertext records start with
an application_data type byte, regardless of their content. This
would of course expand packets by one byte.

> All the best,
>
>         --dkg
>
>
> _______________________________________________
> TLS mailing list
> [email-masked]
> https://www.ietf.org/mailman/listinfo/tls
>
>

---

From: Martin Thomson <[email-masked]>
Date: Fri, 4 Jul 2014 17:38:16 -0700

>   use the first byte for this purpose.

If we want to lose the redundant version number, then these problems need
to be solved. Agree that an update to dtls-srtp should be easy, but the
former is potentially troublesome. An experiment could be expensive, do we
know what is likely here?

---

From: Colm MacCárthaigh <[email-masked]>
Date: Fri, 4 Jul 2014 18:19:18 -0700

> parsed into TLS records by existing network elements.

I don't think that it does. As long as the length field remains
unencrypted, it's possible or a stream processor to figure out where
each record starts and end. Processors already need to do this as new
record types can always be introduced.

-- 
Colm

---

From: [email-masked] (Martin Rex)
Date: Mon, 7 Jul 2014 18:06:43 +0200 (CEST)

Eric Rescorla wrote:
[ Charset UTF-8 unsupported, converting... ]
> Daniel Kahn Gillmor <[email-masked]> > wrote:
>>

This version field is sometimes helpful for debugging, and
using the same formatting of the record contents (clear vs. decrypted)
means less code complexity.

> elements which process the ciphertext.

There are also going to be problems with implementations where the
records are read from network in their entirety before being passed to
and processed by the TLS implementation, and where that network i/o
state machine is currently using the record type to indicate to
higher layers whether the handshake is still ongoing.  It's also
helpful to see in wireshark traces whether a connection was
terminated by a TLS alert, or whether it may have been interrupted
by some middlebox or other networking problem.

What would be the purpose/benefit of this in case that renegotiation
gets dropped?

Such a change at the record layer would reliably preculde that we
ever ship TLSv1.3, because a 2 years ago I've switched to doing
all network i/o outside of the TLS stack and parsing the records above TLS
so that we can arbitrarily mix non-blocking I/O with precise application-
defined network timeouts.

-Martin

---

From: Geoffrey Keating <[email-masked]>
Date: 07 Jul 2014 10:50:18 -0700

> means less code complexity.

I'd be happy with dropping the version field for everything beyond the
first ServerHello.  That should help a bit with code
complexity---ClientHello is already quite special and for ServerHello
it will be the second and third bytes sent on the connection so can be
handled as an exception ("read the first three bytes, process version
number, stuff byte 0 back into the queue").

---

From: Fabrice Gautier <[email-masked]>
Date: Mon, 7 Jul 2014 13:29:41 -0700

If I'm reading this properly, there would be two types of records on
the wire: TLSCiphertext and TLSPlaintext.
However, I don't see a very reliable, documented way to distinguish
between the two on the wire.

I assume that the intention is to distinguish based on the state of
the TLS handshake. i.e.: the record until the first CCS (included) are
TLSPlaintext records, the ones after that are TLSCiphertext records.

I can see a couple scenario where this could be a problem:
- In some case, the piece of code that does the parsing has no
knowledge of the state of the TLS handshake (this is similar to Martin
Rex objection, I think)
- In DTLS, records maybe dropped, received out of order, received
multiple times, etc, so TLSPlaintext and TLSCiphertext records aybe
intermixed, without a clear switchover point. This would make
efficient DTLS difficult.

In practice, I note that, if the ProtocolVersion field stays, it would
be fairly easy to disambiguate TLSCiphertext from TLSPlaintext based
on the first byte, as long as the first byte of the version stays 0x3,
and 0x3 is never a valid ContentType. Except in situations where the
first byte is already overloaded (i.e.:  RFC 5764).

If the ProtocolVersion field is also removed, then there will be
ambiguity between a TLSPlaintext record and a TLSCiphertext of a
specific size.

If the record format needs to change, I suggest allocating a new
reserved ContentType to indicate TLS 1.3 records of all type, and have
the actual content type in the encrypted portion.

I guess that the resulting implementations would not be that different
from what implementations have to do today to support SSL2 ClientHello
records on the server side. (In that case you can tell SSL2 records
because the first byte is 0x80 for SSL2)

In any case, whatever the final TLS 1.3 record format(s) look like, I
hope someone spend some time explaining how an implementer is supposed
to reliably distinguish between the various types of records (SSL2,
TLS 1.2, TLS 1.3) in both the TLS and DTLS scenarios.

---

From: Martin Thomson <[email-masked]>
Date: Mon, 7 Jul 2014 14:01:34 -0700

> between the two on the wire.

TLSPlaintext records appear before a ChangeCipherSpec and
TLSCiphertext records appears afterwards.  That is pretty unambiguous.

Yes, this does mean that it's going to be self-evident in individual
frames, interpreted individually, which is exactly Martin Rex's
objection.

> efficient DTLS difficult.

Doesn't the explicit sequence number in DTLS do that job adequately?
Yes, you don't know until you get the packet containing CCS (or a
retransmit thereof), but there's a fairly narrow range of values where
it could appear.

---

From: Fabrice Gautier <[email-masked]>
Date: Mon, 7 Jul 2014 15:41:20 -0700

> retransmit thereof), but there's a fairly narrow range of values where

How do you reliably get the sequence number out of the records, if you
don't know which format is that record in ?

By extension of the current proposal, I am assuming the DTLS records
on the wire would look like this before CCS:
   type (1 byte) | version (2 bytes) | epoch (2 bytes) | seq_num (6
bytes) | length (2 bytes) | xxx...

and after CCS, records would look like:
   version | epoch | seq_num | length | xxx...
or like this if version is removed:
  epoch | seq_num | length | xxx..

In practice the actual values of type, version and epoch mean that the
value of the first byte will disambiguate the format in (almost) all
cases, but that ought to be documented in the RFC.

---

From: Martin Thomson <[email-masked]>
Date: Mon, 7 Jul 2014 15:55:19 -0700

> don't know which format is that record in ?

   The epoch number is initially zero and is
   incremented each time a ChangeCipherSpec message is sent.

The overlap between the epoch you are likely to encounter (0x0001) and
the content type/upper byte of version is zero.

After that, it's all version-free and there is no ambiguity at all.

Which also leads me to conclude that DTLS-SRTP might be OK here, if we
constrain the epoch.

---

From: Daniel Kahn Gillmor <[email-masked]>
Date: Sun, 13 Jul 2014 12:42:45 -0400

> means less code complexity.

cleartext traffic or the NULL cipher is also helpful for debugging, but
that doesn't mean we should leak it in the TLS protocol.  The code
complexity involved for the endpoints requires no extra state (TLS
engines already know whether they're in NULL_WITH_NULL_NULL or not), and
a amall change to the way the packets are handled in either case.

> What would be the purpose/benefit of this in case that renegotiation

It hides TLS alerts from an observer, for one.  If any attack is mounted
which depends on observing which traffic creates a TLS alert, that
attack would be more difficult. it also hides the possibility of an
outside attacker observing a secondary CCS, which could indicate a
change of keying material.  Having the record types encrypted means that
things like re-keying or mid-stream client authentication (both of which
are currently contemplated even if we get rid of renegotiation) then an
encrypted content-type will obscure that activity from a network observer.

> defined network timeouts.

Above you called the engine you were using a "network i/o state machine"
-- if the machinery actually has state, it should still be able to parse
the length of each record, and can keep track of whether a CCS has been
seen or not, so it should be able to continue parsing each record as it
comes in.

The fact that network-facing code won't know with certainty when a
handshake completes and when application data starts flowing seems like
a feature, not a bug, if we want to protect the communication.

	--dkg

---

From: [email-masked] (Martin Rex)
Date: Mon, 14 Jul 2014 16:12:57 +0200 (CEST)

Daniel Kahn Gillmor wrote:
> a small change to the way the packets are handled in either case.

A TLS records contents and handshake message parser does *NOT* need to know
or care which TLS cipher suite is currently used at the record layer,
only the record layer needs to know that.

> 
> It hides TLS alerts from an observer, for one.
>    [...]
> an encrypted content-type will obscure that activity from a network observer.

As you write yourself, this isn't hiding, but merely obscuring.

GSS-API doesn't have or use any such obscuring.  Does IPsec
have anything like this (or need it, or miss it)?

> a feature, not a bug, if we want to protect the communication.

I definitely see it as a feature.  The bug here would be an unnecessary
difference to existing TLS protocol versions.

For a TLS implementation with blocking network I/O and API semantics
it doesn't really matter whether the handshake can be recognized on
the wire.  For non-blocking APIs, where network I/O is seperated from
the TLS protocol implementation, the difference would be significant
and needlessly complex.

Drop-in replacement of only the TLS stack will be impossible.

-Martin

---

From: Daniel Kahn Gillmor <[email-masked]>
Date: Mon, 14 Jul 2014 10:30:11 -0400

[...]
> I definitely see it as a feature.

I'm glad to hear it :)

> The bug here would be an unnecessary difference to existing TLS protocol versions.
 [...]
> Drop-in replacement of only the TLS stack will be impossible.

Yes, I'm suggesting that for people in the unusual situation of having
moved their network i/o state machine entirely outside of their TLS
stack, they'll want to update their network i/o state machines first (to
understand the new traffic patterns), before upgrading their TLS stacks
to support TLS 1.3.

It seems entirely possible that this will be the case anyway, given the
new handshake flows proposed for TLS 1.3.

I don't think this is a good reason to avoid the improvement.

Regards,

	--dkg

---

From: [email-masked] (Martin Rex)
Date: Mon, 14 Jul 2014 18:10:00 +0200 (CEST)

>> I definitely see it as a feature.

Ooops, my hands wrote the opposite of what my mind was thinking of.

I meant to say that it is a feature of TLS to be able to distinguish
handshake phase from application data phase on the outside.

> understand the new traffic patterns), before upgrading their TLS stacks

That is not ususual at all.  It is actually the most reasonable approach
if you want to do network I/O with arbitrary mixtures of non-blocking
and application-specified timeouts.   Microsoft's SChannel SSP AFAIK has
always been transport-free.  In addition to non-blocking, I also
implemented server-side "TLS extension SNI" purely at the application
layer, the server-side of the SSL stack doesn't care about it.

> I don't think this is a good reason to avoid the improvement.

The more bogus and backwards-incompatible changes go into TLSv1.3,
the more unlikely it will be that we will ever support it.

-Martin

---

From: Watson Ladd <[email-masked]>
Date: Mon, 14 Jul 2014 09:21:52 -0700

> layer, the server-side of the SSL stack doesn't care about it.

No, it isn't. There is no reason that a TLS implementation cannot accept
bytes from the network layer and indicate when it is ready to send on a
message, instead of accepting records.

Sincerely,
Watson Ladd

---

From: Colm MacCárthaigh <[email-masked]>
Date: Mon, 14 Jul 2014 10:51:33 -0700

> handshake phase from application data phase on the outside.

During the week of heartbleed, I wrote a silly kernel module that does
this. I've just put it on GitHub for reference:

https://github.com/colmmacc/nf_conntrack_tls

I wouldn't recommend actually using the module, as it was an emergency
temporary measure borne out of necessity and in general; moving input
processing risks to the kernel is an especially bad idea. But I've
uploaded it as an example of where having the record type in the plain
did help; it allows a middle-layer to do sufficient protocol
validation to protect against a record-level flaw in the application's
record processing (such as Heartbleed).

Even having done that, this still seems like a mis-feature; especially
considering how bad leaking the alert type has been over the years.

-- 
Colm