For TLS with the purpose of liveliness (keep-alive) checks, there's no reason to:
- Encode a payload size field in the heartbeat request/response header (the length of the payload comes from the record layer
rrec.length
in OpenSSL code -- you just have to subtract off the fixed HB header size from this),
- Allow HBs to be variable size -- a small HB size (in the range of ~4-32 bytes) would work perfectly -- just enough for sequence number,
- Add padding to the payload, OR
- Perform PMTU discovery (defined below)
So the design is flawed and overcomplicated in regards to ordinary TLS. Note TLS is the widely used protocol we really care about, encrypting all HTTPS traffic.
In the vulnerable OpenSSL commit all the generated Heartbeat requests have a small fixed payload (18 bytes) and when processing a received HB response, OpenSSL only checks the first two bytes of it which contain the HB sequence number. Source: t1_lib.c (containing all the TLS HB code) when generating a HB (only described in tls1_heartbeat
), it fixes the payload size at 18. Processing a HB response in tls1_process_heartbeat also only does any meaningful processing if the payload is exactly 18. Note processing of a request in TLS is the vulnerable part that undermined HTTPS.
Background
Before getting to the claimed justification, I have to introduce three concepts: DTLS, PMTU, and PMTU discovery that are all unrelated to liveliness checks, but deal with the other proposed use for the Heartbeat extension. Skip to proposed justification if you are familiar.
TLS (encryption on TCP) and DTLS (encryption on UDP)
Regular TLS adds encryption on top of TCP. TCP is a transport layer protocol that provides a reliable transport stream, meaning the application receives a reconstructed data stream with all packets presented to the application in the original order once everything is there, even if some had to wait some extra time for packets to be resent. TCP also provides congestion control (if packets are being dropped because of congestion, TCP will adjust the rate packets are sent). All HTTP, HTTPS, SFTP traffic is sent over TCP.
Datagram TLS (DTLS) is a newer protocol that adds encryption on top of UDP (and similar datagram protocols like DCCP where an application has full control on how to send packets). These are transport layer protocols that do not provide reliable streams, but send packets directly between client/server applications as controlled by an application. With TCP if a packet is lost it automatically gets resent and delays sending further packets until the missing packets get through. UDP gives packet level control to the application, which is often desirable for real-time communication like two-way video chat. If packets A, B, C, D were sent but packet C was lost, it doesn't make sense to either wait for C to be resent before showing packet D to the user -- causing a lengthy pause.
PMTU
For DTLS, it is desirable to know the path maximum transmission unit. An MTU for a single link between routers is maximum packet size that can be sent. Different routers and types of links often support different MTUs. The Path MTU (the smallest MTU on the path your packets take through the network) will generally not be known beforehand as its a property of the path through the network. If you send datagrams that are larger than the PMTU, they would have to fragment at the smallest MTU point which is undesirable for several reasons (inefficient, fragmented packets may be dropped by firewalls/NAT, its confusing to the application layer, and ipv6 by design will never fragment packets). So in the context of DTLS, the RFC forces the data from your record layer to fit in a single DTLS packet (that is smaller than the PMTU). (With TLS these PMTU issues are handled at the TCP level; not the application layer so you TLS can be agnostic to PMTU).
PMTU discovery
There are protocols to discover PMTU — specifically packetization layer Path MTU discovery (RFC 4821). In this context, you probe the network by sending packets of various size (configured to not fragment) and keep track of the upper bound and lower bound of the PMTU depending whether your packets made it through the network or not. This is described in RFC4821. If a probe packet makes it through, you raise the lower bound, if it gets lost you lower the upper bound until the upper/lower bound are close and you have your estimated PMTU, which is used to set upper size on your DTLS packets.
Claimed Justification of HB Having Payload Header, padding, having up to 2-byte size field
The heartbeats RFC RFC6520 says you can use Heartbeats for path MTU discovery for DTLS:
5.1. Path MTU Discovery
DTLS performs path MTU discovery as described in Section 4.1.1.1 of
[RFC6347]. A detailed description of how to perform path MTU
discovery is given in [RFC4821]. The necessary probe packets are the
HeartbeatRequest messages.
DTLS applications do need to estimate PMTU. However, this is not done by DTLS, its done by the application using DTLS. Looking at the quoted section of the DTLS RFC Section 4.1.1.1 of RFC6347 it states "In general, DTLS's philosophy is to leave PMTU discovery to the application." It continues to give three caveats for why DTLS has to worry about PMTU (DTLS applications must subtract off the DTLS header to get effective PMTU size for data, DTLS may have to communicate ICMP "Datagram Too Big" back to the application layer, and DTLS handshakes should be smaller than PMTU. Earlier in the DTLS RFC it declares the DTLS record MUST fit in a single datagram smaller than the PMTU, so PMTU discovery/estimation must be done by the application using DTLS.
In PMTU discovery it makes sense to have a small field describing the length of the payload, have a large amount of arbitrary padding, and having something that echos back, yup got your request with this size MTU even though I'm only sending you back the sequence number (and for efficiency can drop the padding on the response). Granted it doesn't make sense if you describe the size of the payload to allow the payload to be bigger than about ~4-32 bytes, so payload size could be fixed or described by a one byte field, even if arbitrarily long padding could be concatenated.
Analysis of Claim
OpenSSL HB implementation and description in the HB RFC, doesn't describe or perform this PMTU discovery protocol. MTU is not present in the code. OpenSSL does provide only one mechanism to generate a HB request in TLS and DTLS, but it is of fixed size (18 byte payload, not configurable).
- There's no functionality to send a sequence of HBs to probe for PMTU discovery in the OpenSSL code, or detailed description of how HBs are used in a probing process,
- There's no indication that HBs are configured to not fragment (so they could even be used in this probing manner),
- If you wanted to use HB to do PMTU discovery, the application writer would have to write all the code themselves for client and server.
- The payload field even in the context of finding PMTU only needs to be one byte (even if its not fixed)
- There's no reason for them not to just do the entire probing process to use a HB packet versus any arbitrary type of packet in their client/server applications; e.g., using an ordinary UDP packet.
- PMTU discovery only makes sense in context of DTLS, so why are these completely unnecessary features present in TLS heartbeats -- applications that do not need to be PMTU aware?
At best this was a seriously flawed design (in TLS) incorporating a YAGNI features, that was then coded up badly to fully trust a user provided header field without any sanity testing. At worst, the PMTU sections were just a complicated cover story to allow insertion of vulnerable code that provides some semblance of justification.
Searching through the IETF TLS mailing list
If you search the IETF TLS mailing list, you can find interesting nuggets. Why is the payload/padding length uint16, and why is there padding if its to be discarded? PMTU discovery. The same asker (Juho Vähä-Herttua) states he would strongly prefer packet verification: read payload length, padding length, and verify that it matches record length (minus header). Also Simon Josefsson:
I have one mild concern with permitting arbitrary payload. What is the
rationale for this? It opens up for a side channel in TLS. It could
also be abused to send non-standardized data. Further, is there any
reason to allow arbitrary sized payload? In my opinion, the
payload_length, payload and padding fields seems unnecessary to me.
Michael Tüxen's response is largely inadequate (maybe some feature wants to be added on top to say calculate RTT) and summarizes with "The point here is that for interoperability, it
does not matter what the payload is, it is only important that it is
reflected."
Also of note reason for random padding "[we] randomize ... the data in the heartbeat message to attempt to head of any issues occurring from weak or flawed ciphers. " followed by question "Are there any papers or cipher documentation discussing how using randomized data in a packet would solve possible future cipher flaws?", followed by an paper on deterministic authenticated encryption. The response is great:
Indeed, but this is not a generic encryption mode like CBC or CTR. It
is specifically designed to encrypt random keys, and thus depends on
its randomness. Typical encryption modes are specifically designed to
prevent someone distinguishing a given plaintext encryption from a
random one.
Now, if one would like to use a subliminal channel in TLS, the
heartbeat extension now provides an unbounded channel.
It's interesting that that comment was never addressed. (Other than someone else commenting " Well that's a whole different issue....").