RFC 8548

Cryptographic Protection of TCP Streams (tcpcrypt)

๐Ÿ“… published May 2019, from ietf

RFC 8548: Cryptographic Protection of TCP Streams (tcpcrypt) ๐Ÿฆ‹

Okay bestie, let's get the tea on this one. The title block says this is RFC 8548, published in May 2019 by the IETF, with ISSN 2070-1721. It's Experimental, so it's a "let's try it and see" doc and not a Standards Track spec ๐Ÿงช. The whole thing is about tcpcrypt (Cryptographic Protection of TCP Streams), and it's written by a squad of six: A. Bittau (Google), D. Giffin (Stanford University), M. Handley (University College London), D. Mazieres (Stanford University), Q. Slack (Sourcegraph), and E. Smith (Kestrel Institute) ๐Ÿ‘‘. The title block doesn't list anything it obsoletes or updates, so it's not replacing or changing any other RFC ๐Ÿ’….

Abstract

Okay so tcpcrypt is a TCP encryption protocol that's built to work with the TCP Encryption Negotiation Option (TCP-ENO) ๐Ÿ”. What's giving? It plays nicely with middleboxes, meaning it keeps working even when things like resegmentation, NATs, and other messing-around with the TCP header happen ๐Ÿซถ. It's self-contained and tailored to TCP implementations, which is a big deal because those often live in kernels or other places where huge external software dependencies are a no-go ๐Ÿ™ˆ.

The catch is that TCP options have a size limit, so tcpcrypt needs one extra one-way message latency to do key exchange before any application data can go out โณ. But here's the good part: two hosts that recently had a tcpcrypt connection can skip that extra wait ๐Ÿš€.

Status of This Memo

This doc is not an Internet Standards Track spec. It's published for examination, experimental implementation, and evaluation ๐Ÿงช.

It defines an Experimental Protocol for the Internet community โœจ. It's a product of the IETF, it represents IETF consensus, it got public review, and the IESG approved it for publication ๐Ÿ’–. Not every IESG-approved doc is up for any level of Internet Standard, so check Section 2 of RFC 7841 if you want the deets ๐Ÿค“.

Info on the current status, errata, and how to give feedback lives at https://www.rfc-editor.org/info/rfc8548 ๐Ÿ”—.

Copyright (c) 2019 IETF Trust and the persons identified as the document authors. All rights reserved ๐ŸŽ€.

This doc is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) as they stand on the publication date. Please read these carefully, because they spell out your rights and restrictions for this doc ๐Ÿ“œ. Code Components pulled out of this doc must include Simplified BSD License text, as described in Section 4.e of the Trust Legal Provisions, and they come with no warranty, as described in the Simplified BSD License ๐ŸŒธ.


Table of Contents

1.  Introduction  . . . . . . . . . . . . . . . . . . . . . . . .   4
2.  Requirements Language . . . . . . . . . . . . . . . . . . . .   4
3.  Encryption Protocol . . . . . . . . . . . . . . . . . . . . .   4
  3.1.  Cryptographic Algorithms  . . . . . . . . . . . . . . . .   4
  3.2.  Protocol Negotiation  . . . . . . . . . . . . . . . . . .   6
  3.3.  Key Exchange  . . . . . . . . . . . . . . . . . . . . . .   7
  3.4.  Session ID  . . . . . . . . . . . . . . . . . . . . . . .  10
  3.5.  Session Resumption  . . . . . . . . . . . . . . . . . . .  10
  3.6.  Data Encryption and Authentication  . . . . . . . . . . .  14
  3.7.  TCP Header Protection . . . . . . . . . . . . . . . . . .  16
  3.8.  Rekeying  . . . . . . . . . . . . . . . . . . . . . . . .  16
  3.9.  Keep-Alive  . . . . . . . . . . . . . . . . . . . . . . .  17
4.  Encodings . . . . . . . . . . . . . . . . . . . . . . . . . .  18
  4.1.  Key-Exchange Messages . . . . . . . . . . . . . . . . . .  18
  4.2.  Encryption Frames . . . . . . . . . . . . . . . . . . . .  20
    4.2.1.  Plaintext . . . . . . . . . . . . . . . . . . . . . .  20
    4.2.2.  Associated Data . . . . . . . . . . . . . . . . . . .  21
    4.2.3.  Frame ID  . . . . . . . . . . . . . . . . . . . . . .  21
  4.3.  Constant Values . . . . . . . . . . . . . . . . . . . . .  22
5.  Key-Agreement Schemes . . . . . . . . . . . . . . . . . . . .  22
6.  AEAD Algorithms . . . . . . . . . . . . . . . . . . . . . . .  24
7.  IANA Considerations . . . . . . . . . . . . . . . . . . . . .  24
8.  Security Considerations . . . . . . . . . . . . . . . . . . .  25
  8.1.  Asymmetric Roles  . . . . . . . . . . . . . . . . . . . .  27
  8.2.  Verified Liveness . . . . . . . . . . . . . . . . . . . .  27
  8.3.  Mandatory Key-Agreement Schemes . . . . . . . . . . . . .  27
9.  Experiments . . . . . . . . . . . . . . . . . . . . . . . . .  28
10. References  . . . . . . . . . . . . . . . . . . . . . . . . .  29
  10.1.  Normative References . . . . . . . . . . . . . . . . . .  29
  10.2.  Informative References . . . . . . . . . . . . . . . . .  30
Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . .  31
Contributors  . . . . . . . . . . . . . . . . . . . . . . . . . .  31
Authors' Addresses  . . . . . . . . . . . . . . . . . . . . . . .  31

1. Introduction โœจ

Okay, so tcpcrypt is an extension to TCP that adds cryptographic protection for session data ๐Ÿ”’. It was designed to hit these goals:

  • Meet the requirements of the TCP Encryption Negotiation Option (TCP-ENO) [RFC8547] for protecting connection data ๐Ÿ’….
  • Be easy to implement as a small, self-contained piece inside TCP stacks ๐Ÿงฉ.
  • Keep the extra latency at connection startup to a minimum โฑ๏ธ.
  • As much as possible, avoid connection failures when there are NATs and other middleboxes that might normalize traffic or otherwise mess with TCP segments ๐Ÿ›ก๏ธ.
  • Work independently of IP addresses, so resumed sessions can still be authenticated efficiently even when either end changes its IP address ๐Ÿฆ‹.

There's also a companion doc, [TCPINC-API], that describes recommended interfaces for configuring certain parameters of this protocol ๐ŸŽ€.

2. Requirements Language ๐Ÿ“‹

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" are the BCP 14 key words in this doc. They get read as described in BCP 14 [RFC2119] [RFC8174], but only when they show up in all capitals, exactly like this ๐Ÿง.

3. Encryption Protocol ๐Ÿ”

This section is where the tcpcrypt protocol actually operates ๐Ÿ’–. The wire format of all messages is specified in Section 4, so go there for the bits on the wire ๐Ÿ“ฆ.

3.1. Cryptographic Algorithms ๐Ÿงฎ

Setting up a tcpcrypt connection uses three types of cryptographic algorithms, and honestly each one has a job ๐Ÿ‘‘:

  • A key agreement scheme is used with a short-lived public key to agree upon a shared secret ๐Ÿค.
  • An extract function is used to generate a pseudo-random key (PRK) from some initial keying material produced by the key agreement scheme ๐ŸŒˆ. The notation Extract(S, IKM) denotes the output of the extract function with salt S and initial keying material IKM ๐Ÿง‚.
  • A collision-resistant pseudo-random function (CPRF) is used to generate multiple cryptographic keys from a pseudo-random key, which is typically the output of the extract function ๐Ÿ”‘. The CPRF produces an arbitrary amount of Output Keying Material (OKM), and the notation CPRF(K, CONST, L) designates the first L bytes of the OKM that the CPRF produces when it's parameterized by key K and the constant CONST โœจ.

The Extract and CPRF functions used by the tcpcrypt variants in this doc are the Extract and Expand functions of the HMAC-based Key Derivation Function (HKDF) [RFC5869], which is built on Keyed-Hashing for Message Authentication (HMAC) [RFC2104] ๐ŸŽ€. They're defined like this in terms of the function HMAC-Hash(key, value) for a negotiated Hash function such as SHA-256. The symbol | means concatenation, and the counter concatenated to the right of CONST takes up a single octet ๐Ÿ“.

        HKDF-Extract(salt, IKM) -> PRK
           PRK = HMAC-Hash(salt, IKM)

        HKDF-Expand(PRK, CONST, L) -> OKM
           T(0) = empty string (zero length)
           T(1) = HMAC-Hash(PRK, T(0) | CONST | 0x01)
           T(2) = HMAC-Hash(PRK, T(1) | CONST | 0x02)
           T(3) = HMAC-Hash(PRK, T(2) | CONST | 0x03)
           ...

           OKM  = first L octets of T(1) | T(2) | T(3) | ...
           where L <= 255*OutputLength(Hash)

Figure 1: HKDF Functions Used for Key Derivation ๐Ÿ“

Last but definitely not least, once tcpcrypt is successfully set up and the encryption keys are derived, an algorithm for Authenticated Encryption with Associated Data (AEAD) protects the confidentiality and integrity of all transmitted application data ๐Ÿ›ก๏ธ๐Ÿ’–. AEAD algorithms use a single key both to encrypt their input data and to generate a cryptographic tag that comes along with the resulting ciphertext ๐Ÿ”. When decryption happens, that tag lets you authenticate the encrypted data and any optional associated plaintext data ๐Ÿซถ.

3.2. Protocol Negotiation ๐ŸŽ€

Okay so tcpcrypt leans on TCP-ENO [RFC8547] to figure out whether encryption gets switched on for a connection ๐Ÿ” and which key-agreement scheme to use. TCP-ENO negotiates a particular TCP encryption protocol (TEP) by sticking protocol identifiers into ENO suboptions. This document links four TEP identifiers to the tcpcrypt protocol, listed in Table 4 of Section 7. Each identifier means a specific key-agreement scheme, with its own CPRF and length parameter. Future standards can add more TEP identifiers to tcpcrypt, as long as they follow the assignment policy in TCP-ENO. ๐Ÿ’…

An active opener that wants to negotiate tcpcrypt includes an ENO option in its SYN segment ๐Ÿš€. That option carries suboptions with tcpcrypt TEP identifiers, which show the key-agreement schemes it's willing to turn on. The active opener MAY also include suboptions for encryption protocols other than tcpcrypt, plus global suboptions as TCP-ENO describes. Extra options, all fine ๐ŸŒˆ

If a passive opener gets an ENO option with tcpcrypt TEPs it supports, it MAY then attach an ENO option to its SYN-ACK segment. In that reply it includes only the one TEP it wants to enable. Just one, bestie, keep it simple ๐Ÿฆ‹

To give the two hosts distinct roles in each connection, tcpcrypt relies on the role-negotiation mechanism of TCP-ENO. As one result of that negotiation, TCP-ENO gives the hosts abstract roles called "A" at one end and "B" at the other. Usually the active opener is "A" and the passive opener is "B", but during a simultaneous open, an extra mechanism breaks the tie and gives each host its own role ๐Ÿ™ˆ. TCP-ENO uses the terms "host A" and "host B" to pin down each end of a connection, and this document uses them the same way ๐Ÿ’œ

An ENO suboption has a flag "v" that says whether there's variable-length data attached. To propose fresh key agreement with a particular tcpcrypt TEP, a host sends a one-byte suboption with the TEP identifier and v = 0. To propose session resumption (more on that below) with a particular TEP, a host sends a variable-length suboption. That one has the TEP identifier, the flag v = 1, an identifier derived from a session secret previously negotiated with the same host and the same TEP, and a nonce ๐Ÿงš

Once two hosts have swapped SYN segments, TCP-ENO defines the negotiated TEP as the last valid TEP identifier in host B's SYN segment (that's the passive opener when there's no simultaneous open) that also shows up in host A's. If there's no such TEP, hosts MUST disable TCP-ENO and tcpcrypt. No match, no encryption, that's the vibe ๐Ÿ’€

If the negotiated TEP was sent by host B with v = 0, fresh key agreement gets performed as described in Section 3.3. If, on the other hand, host B sent the TEP with v = 1 and both hosts sent appropriate resumption identifiers in their suboption data, the key-exchange messages get skipped. Instead, keys come from session resumption as described in Section 3.5. With session resumption, protected application data MAY be sent immediately as detailed in Section 3.6 โญ

Note that the negotiated TEP gets picked without looking at the "v" bits in ENO suboptions. So if host A offers resumption with a particular TEP and host B replies with a non-resumption suboption using that same TEP, that TEP can become the negotiated one, and then fresh key agreement happens. ๐Ÿคฏ In other words, sending a resumption suboption also means you're willing to do fresh key agreement with that TEP. Plot twist, but it makes sense ๐ŸŒธ

As REQUIRED by TCP-ENO, once a host has both sent and received an ACK segment containing a valid ENO option, encryption MUST be enabled and plaintext application data MUST NOT ever be exchanged on the connection. ๐Ÿ”’ If the negotiated TEP is among those listed in Table 4, a host MUST follow the protocol described in this document. No shortcuts, periodt ๐Ÿ‘‘

3.3. Key Exchange ๐Ÿ”‘

Once a tcpcrypt TEP has been successfully negotiated, all further signaling happens in the Data portion of TCP segments ๐Ÿ“ฆ. Except when resumption was negotiated (see Section 3.5), the two hosts do key exchange through two messages, Init1 and Init2, at the start of the data streams of host A and host B, respectively. These messages MAY span multiple TCP segments and don't have to end at a segment boundary. However, the segment containing the last byte of an Init1 or Init2 message MUST have TCP's push flag (PSH) set. The PSH flag is the finish line ๐Ÿ

The key exchange protocol, in the abstract, goes like this:

    A -> B:  Init1 = { INIT1_MAGIC, sym_cipher_list, N_A, Pub_A }
    B -> A:  Init2 = { INIT2_MAGIC, sym_cipher, N_B, Pub_B }

The concrete format of these messages is specified in Section 4.1 ๐Ÿ“

Here's what each parameter means ๐Ÿง

  • INIT1_MAGIC, INIT2_MAGIC: Constants defined in Section 4.3.
  • sym_cipher_list: A list of identifiers of symmetric ciphers (AEAD algorithms) that host A finds acceptable. These are specified in Table 5 of Section 7.
  • sym_cipher: The symmetric cipher that host B picks from the sym_cipher_list host A sent.
  • N_A, N_B: Nonces chosen at random by hosts A and B, respectively.
  • Pub_A, Pub_B: Ephemeral public keys for hosts A and B, respectively. These, along with their matching private keys, are short-lived values that MUST be refreshed frequently โณ. The private keys SHOULD NOT ever be written to persistent storage. The security risks of storing these keys are discussed in Section 8. Not giving your keys a long life is giving healthy boundaries ๐Ÿ’–

If a host gets an ephemeral public key from its peer and a key-validation step fails (see Section 5), it MUST abort the connection and raise an error condition that's distinct from the end-of-file condition. ๐Ÿšจ

The ephemeral secret ES is the result of the key-agreement algorithm (see Section 5) indicated by the negotiated TEP. The inputs to the algorithm are the local host's ephemeral private key and the remote host's ephemeral public key. For example, host A computes ES using its own private key (which never gets transmitted) and host B's public key, Pub_B ๐Ÿ—๏ธ

Then both sides compute a pseudo-random key, PRK, which all session secrets come from:

       PRK = Extract(N_A, eno_transcript | Init1 | Init2 | ES)

In that formula, "|" means concatenation. eno_transcript is the protocol-negotiation transcript defined in Section 4.8 of [RFC8547], and Init1 and Init2 are the transmitted encodings of the messages described in Section 4.1 ๐Ÿงฉ

A series of session secrets gets computed from PRK like this:

              ss[0] = PRK
              ss[i] = CPRF(ss[i-1], CONST_NEXTK, K_LEN)

The value ss[0] is used to generate all key material for the current connection ๐ŸŒŸ. The values ss[i] for i > 0 are used by session resumption, so subsequent connections between the same two hosts can skip public key cryptography (see Section 3.5). The CONST_* values are constants defined in Section 4.3. The length K_LEN depends on the tcpcrypt TEP in use, and it's specified in Section 5.

Given a session secret ss[i], the two sides compute a series of master keys like so:

           mk[0] = CPRF(ss[i], CONST_REKEY | sn[i], K_LEN)
           mk[j] = CPRF(mk[j-1], CONST_REKEY, K_LEN)

Moving along that series of master keys is described in Section 3.8 ๐Ÿ”„. The values represented by sn[i] are session nonces. For the initial session, where i = 0, the session nonce is zero bytes long. The values for later sessions come from fresh connection data, as described in Section 3.5.

Last step: each master key mk[j] generates traffic keys for protecting application data with authenticated encryption ๐Ÿ›ก๏ธ

    k_ab[j] = CPRF(mk[j], CONST_KEY_A, ae_key_len + ae_nonce_len)
    k_ba[j] = CPRF(mk[j], CONST_KEY_B, ae_key_len + ae_nonce_len)

In the first session from fresh key agreement, host A uses traffic keys k_ab[j] to encrypt, and host B uses them to decrypt. Meanwhile, host B uses k_ba[j] to encrypt, and host A uses those to decrypt. โ†”๏ธ In a resumed session (more thoroughly covered in Section 3.5), each host uses the keys the same way it did in the original session, no matter what role it has in the current session. For example, if a host played role "A" in the first session, it'll use k_ab[j] to encrypt in every derived session. Consistency is everything ๐Ÿ’…

The values ae_key_len and ae_nonce_len depend on the authenticated-encryption algorithm selected, and they're given in Table 3 of Section 6. The algorithm uses the first ae_key_len bytes of each traffic key as an authenticated-encryption key, and it uses the following ae_nonce_len bytes as a nonce randomizer ๐ŸŽฒ

Implementations SHOULD provide an interface that lets the user specify, for a particular connection, the set of AEAD algorithms to advertise in sym_cipher_list (when playing role "A") and the order of preference for picking an algorithm from those offered (when playing role "B") โš™๏ธ. A companion document [TCPINC-API] describes recommended interfaces for this purpose ๐Ÿ“š

After host B sends Init2, or host A receives it, that host MAY immediately begin transmitting protected application data, as described in Section 3.6 ๐Ÿš€

If host A receives an Init2 with a sym_cipher value that wasn't in the sym_cipher_list it sent in Init1, it MUST abort the connection and raise an error condition distinct from the end-of-file condition. ๐Ÿ˜ฑ Yikes, that's a hard no.

Throughout this document, to "abort the connection" means issuing the "Abort" command described in Section 3.8 of [RFC793]. In other words, the TCP connection gets destroyed, RESET is transmitted, and the local user is alerted to the abort event ๐Ÿšจ

3.4. Session ID ๐Ÿชช

TCP-ENO requires each TEP to define a session ID value that uniquely identifies each encrypted connection ๐Ÿ†”

A tcpcrypt session ID begins with the byte host B transmits that contains the negotiated TEP identifier along with the "v" bit. The rest of the ID is derived from the session secret and session nonce, like this:

 session_id[i] = TEP-byte | CPRF(ss[i], CONST_SESSID | sn[i], K_LEN)

Again, the length K_LEN depends on the TEP and is specified in Section 5 โœจ

3.5. Session Resumption โœจ

okay so if two hosts already negotiated a session with secret ss[i-1], bestie, they can connect again WITHOUT any public-key operations ๐Ÿ”ฅ they just use ss[i], the next secret in the chain that was derived from the original PRK. so efficient, it's giving main character energy ๐Ÿ’…

to say "hey, I wanna resume this session," a host sends a SYN segment with a resumption suboption. that's an ENO suboption carrying three things: the negotiated TEP identifier from the previous session, half of the resumption identifier for the new session, and a resumption nonce ๐ŸŽ€

The resumption nonce MUST have a minimum length of zero bytes and a maximum length of eight bytes. The value MUST be picked randomly, or with a mechanism that guarantees uniqueness even if a VM gets cloned or the same session gets re-executed ๐Ÿ™ˆ

ngl this is the scariest part ๐Ÿ’€ an attacker who can force either side of a connection to reuse a session secret with the same nonce will completely break the security of tcpcrypt. reuse can happen with VM cloning or when system-level hibernation state gets reused, so be careful bestie ๐Ÿ˜ญ

Implementations SHOULD give you an API for setting the resumption nonce length, and they MUST default to eight bytes if they can't prevent session secret reuse ๐Ÿซถ

The resumption identifier is calculated from a session secret ss[i] like this:

resume[i] = CPRF(ss[i], CONST_RESUME, 18)

To name a session for resumption, a host sends either the first or the second half of the resumption identifier, depending on the role it played in the original session with secret ss[0] ๐Ÿฆ‹

A host that originally played role "A" and wants to resume from a cached session sends a suboption with the first half of the resumption identifier:

     byte     0      1             9      10
          +------+------+--...--+------+------+--...--+------+
          | TEP- |   resume[i]{0..8}   |       nonce_a       |
          | byte |                     |                     |
          +------+------+--...--+------+------+--...--+------+

Figure 2: Resumption suboption sent when original role was "A".

The TEP-byte contains a tcpcrypt TEP identifier and v = 1. The nonce value MUST have a length between 0 and 8 bytes ๐ŸŒธ

Similarly, a host that originally played role "B" sends a suboption with the second half of the resumption identifier:

     byte     0      1             9      10
          +------+------+--...--+------+------+--...--+------+
          | TEP- |   resume[i]{9..17}  |       nonce_b       |
          | byte |                     |                     |
          +------+------+--...--+------+------+--...--+------+

Figure 3: Resumption suboption sent when original role was "B".

The TEP-byte contains a tcpcrypt TEP identifier and v = 1. The nonce value MUST have a length between 0 and 8 bytes ๐Ÿงš

If a passive opener gets a resumption suboption with an identifier-half that names a session secret it has cached, and the suboption's TEP matches the TEP used in the previous session, it SHOULD agree to resume (with exceptions, specified below) by sending its own resumption suboption containing the other half of the identifier โœจ Otherwise, it MUST NOT agree to resumption ๐Ÿ™…โ€โ™€๏ธ

If a passive opener doesn't agree to resume with a particular TEP, it MAY either ask for a fresh key exchange by replying with a non-resumption suboption using the same TEP, or respond to any other TEP suboption it received ๐Ÿ’œ

If a passive opener gets an ENO suboption with a TEP identifier and v = 1, but the suboption data is less than 9 bytes long, it MUST behave as if that same TEP had been sent with v = 0. In other words, the suboption MUST be read as an offer to negotiate a fresh key exchange with that TEP ๐Ÿ“

If an active opener sends a resumption suboption with a particular TEP and the appropriate half of a resumption identifier, and then, in the same TCP handshake, gets a resumption suboption with the same TEP and an identifier-half that does NOT match that resumption identifier, it MUST ignore that suboption. In the typical case where this was the only ENO suboption received, the host MUST disable TCP-ENO and tcpcrypt. It MUST NOT send any more ENO options and MUST NOT encrypt the connection ๐Ÿ˜ญ๐Ÿ™ˆ

When a host concludes that TCP-ENO negotiation has succeeded for some TEP that was received in a resumption suboption, it MUST then enable encryption with that TEP using the cached session secret. To do this, it first builds sn[i] like so:

sn[i] = nonce_a | nonce_b

Master keys are then computed from s[i] and sn[i], as described in Section 3.3, as well as from application data encrypted as described in Section 3.6 ๐Ÿ”ฅ

The session ID (Section 3.4) is built the same way for resumed sessions as it is for fresh ones. In this case the first byte will always have v = 1. The rest of the ID is derived from the cached session secret and the session nonce that was generated during resumption ๐Ÿฆ‹

In a simultaneous open where TCP-ENO is able to establish asymmetric roles, two hosts that simultaneously send SYN segments with compatible resumption suboptions MAY resume the associated session ๐Ÿ‘‘

In a particular SYN segment, a host SHOULD NOT send more than one resumption suboption (because that eats up TCP option space and is unlikely to be a useful practice), and it MUST NOT send more than one resumption suboption with the same TEP identifier. But in addition to any resumption suboptions, an active opener MAY include non-resumption suboptions describing other TEPs it supports (in addition to the TEP in the resumption suboption) ๐ŸŒˆ

After using the session secret ss[i] to compute mk[0], implementations SHOULD compute and cache ss[i+1] for possible use by a later session, and then erase ss[i] from memory. Hosts MAY keep ss[i+1] until it gets used or the memory needs to be reclaimed. Hosts SHOULD NOT write any session secrets to non-volatile storage ๐Ÿ™ˆ๐Ÿ’…

When proposing resumption, the active opener MUST use the lowest value of "i" that hasn't already been used (successfully or not) to negotiate resumption with the same host and for the same original session secret ss[0] โญ

A given session secret ss[i] MUST NOT be used to secure more than one TCP connection. To prevent this, a host MUST NOT resume with a session secret if it has EVER enabled encryption with that same secret in the past, in either role ๐Ÿšซ If two hosts simultaneously send SYN segments to each other that propose resumption with the same session secret, but the two segments are not part of a simultaneous open, both connections would need to fall back to fresh key exchange. To avoid this limitation, implementations MAY choose to implement session resumption so that all session secrets derived from a given ss[0] are used for either passive or active opens at the same host, but not both ๐ŸŽ€

If two hosts have previously negotiated a tcpcrypt session, either host MAY later start session resumption, regardless of which host was the active opener or played the "A" role in the previous session ๐Ÿ’–

However, a given host MUST either encrypt with keys k_ab[j] for all sessions derived from the same original session secret ss[0], or with keys k_ba[j]. So which keys a host uses to send segments is NOT affected by the role it plays in the current connection. It depends only on whether the host played the "A" or "B" role in the initial session ๐Ÿง 

Implementations that cache session secrets MUST provide a way for applications to control that caching. In particular, when an application requests a new TCP connection, it MUST have a way to specify two policies for the duration of the connection: 1) that resumption requests will be ignored, so fresh key exchange will be necessary; and 2) that no session secrets will be cached. (These policies can be specified independently or together as a unit.) And for an established connection, an application MUST have a means to cause any cache state that was used in or resulted from establishing the connection to be flushed. A companion document, [TCPINC-API], describes recommended interfaces for this purpose ๐Ÿซถ๐ŸŒธ

3.6. Data Encryption and Authentication โœจ

okay so once the key exchange is done (or skipped, thanks to session resumption ๐Ÿ’…), every bit of communication on a tcpcrypt-enabled connection gets wrapped up in delimited encryption frames. Those frames are encrypted and authenticated with the keys you agreed on. main character energy, fr fr ๐Ÿ”ฅ

The protection comes from AEAD (Authenticated Encryption with Associated Data) algorithms ๐Ÿ”. The allowed ones are listed in Table 5 of Section 7, and more can be added later under the policy in that same section. One algorithm gets picked during the negotiation from Section 3.3. The ae_key_len and ae_nonce_len values for each algorithm live in Table 3 of Section 6, along with the list of which algorithms MUST be implemented ๐ŸŽ€.

The frame format is in Section 4.2 ๐Ÿฆ‹. A sending host chops its stream of application data into a bunch of chunks. Each chunk goes into the data field of a plaintext value, and that gets encrypted into the frame's ciphertext field. The chunks MUST be small enough that the ciphertext (which depends on the AEAD cipher and is usually a little longer than the plaintext) comes out under 2^16 bytes, so it's less than that length. no cap, it's a hard limit ๐Ÿ™ˆ

Next, an "associated data" value (see Section 4.2.2) gets built for each frame. It holds the frame's control field and the length of the ciphertext ๐Ÿงš.

There's also a "frame ID" value (see Section 4.2.3) built for each frame, but it never actually gets sent over the wire ๐ŸŒธ. It's a 64-bit offset field, and its integer value is the zero-indexed byte offset where the current encryption frame starts in the underlying TCP datastream. (Note: that's the offset in the framing stream, not the plaintext application stream ๐Ÿ™ƒ.) Then the offset gets left-padded with zero-valued bytes until it's exactly ae_nonce_len long. This part is seriously important: because the security of the AEAD algorithms in this document depends on it, an implementation MUST NOT ever transmit distinct frames with the same frame ID value under the same encryption key ๐Ÿ’€. Retransmitted TCP segments MUST contain the same payload bytes for the same TCP sequence numbers. And a host MUST NOT transmit more than 2^64 bytes in the underlying TCP datastream before rekeying as described in Section 3.8, because that would make the offset field wrap around ๐Ÿ˜ญ.

The AEAD keys come from the traffic key k_ab[j] or k_ba[j] (for some "j"), depending on the host's role from Section 3.3 ๐Ÿ—๏ธ. First, the traffic key gets split into two parts:

                                     ae_key_len + ae_nonce_len - 1
                                                      |
       byte  0                    ae_key_len          |
             |                           |            |
             v                           v            v
           +----+----+--...--+----+----+----+--...--+----+
           |             K             |        NR       |
           +----+----+--...--+----+----+----+--...--+----+

Figure 4: Format of Traffic Key

Following the "AEAD Interface" from Section 2 of [RFC5116], the first ae_key_len bytes of the traffic key give you the AEAD key K ๐Ÿ”‘. The leftover ae_nonce_len bytes give you a nonce randomizer value NR. That gets XORed with the frame ID to make N, the AEAD nonce for the frame โœจ:

N = frame_ID XOR NR

The other two AEAD inputs, P and A, come from the frame's plaintext value and associated data, respectively. The output of the AEAD operation, C, is what gets sent in the frame's ciphertext field ๐Ÿš€.

When a frame shows up, tcpcrypt rebuilds the associated data and frame ID values (the associated data only has stuff that was sent in the clear, and the frame ID is implicit in the TCP stream) ๐Ÿงฉ. Then it works out the nonce N the same way as above, and hands those plus the ciphertext to the AEAD decryption operation. That operation returns either a plaintext value P or the special symbol FAIL. If it's FAIL, the implementation SHOULD abort the connection and raise an error condition that's different from the end-of-file condition ๐Ÿšจ. But if none of the TCP segment(s) holding the frame have been acknowledged, and retransmitting them could possibly result in a valid frame, the implementation MAY drop those segments instead. In that case it can renege on them if they were SACKed, following Section 8 of [RFC2018] ๐Ÿ™.


3.7. TCP Header Protection

the ciphertext field of each encryption frame carries protected versions of certain TCP header values ๐Ÿ›ก๏ธ. It's giving security, lowkey iconic.

When the URGp bit is set, the urgent field tells you an offset from the current frame's starting offset. Add up those offsets and you get the index of the last byte of urgent data in the application datastream ๐Ÿš‘.

A sender MUST set the FINp bit on the last frame it sends in the connection (unless it's aborting the connection), and it MUST NOT set FINp on any other frame ๐Ÿ. Not me setting FINp on the wrong frame, bestie ๐Ÿ™…โ€โ™€๏ธ.

TCP normally sets the FIN flag when a sender runs out of data. With tcpcrypt, that means setting FIN on the segment that holds the last byte of the last frame ๐ŸŽฌ. But here's the twist: a receiver MUST report the end-of-file condition to the connection's local user when, and only when, it receives a frame with the FINp bit set ๐Ÿง. If a host gets a segment with the TCP FIN flag set, but the datastream it's received so far (including that segment) doesn't have a frame with FINp set, the host SHOULD abort the connection and raise an error condition that's different from the end-of-file condition ๐Ÿ˜ค. But if there are unacknowledged segments that could possibly turn into a valid frame when retransmitted, the host MAY drop the segment with the TCP FIN flag set instead. It can renege on it if it's been SACKed, following Section 8 of [RFC2018] ๐ŸŒˆ.


3.8. Rekeying

Rekeying lets hosts wipe keys out of memory that could otherwise decrypt segments they already sent ๐Ÿงน. It also makes it possible to use AEAD ciphers that can only safely encrypt a limited number of messages under a single key ๐Ÿ”„.

As Section 3.3 described, a master key mk[j] generates two encryption keys, k_ab[j] and k_ba[j]. We call these a key set with generation number "j" ๐Ÿ—‚๏ธ. Every host keeps two numbers: a local generation number, which decides which key set it uses to encrypt outgoing frames, and a remote generation number, which equals the highest generation used in frames received from its peer ๐Ÿ“ˆ. Both of these start at zero.

A host MAY increment its local generation number past the remote generation number it has recorded. We call that "initiating rekeying" ๐ŸŽ‰.

When a host has bumped its local generation number and uses the new key set to encrypt an outgoing frame for the very first time, it MUST set rekey = 1 for that frame. It MUST set rekey = 0 in all other cases ๐Ÿ”ข.

When a host receives a frame with rekey = 1, it increments its record of the remote generation number. If that remote generation number is now bigger than the local generation number, the receiver MUST immediately increment its local generation number to match ๐Ÿชž. Also, if the receiver hasn't transmitted a segment with the FIN flag set yet, it MUST immediately send a frame (with empty application data if necessary) with rekey = 1 ๐Ÿ“จ.

A host MUST NOT initiate more than one concurrent rekey operation if it has no data to send. In other words, it MUST NOT initiate rekeying with an empty encryption frame more than once while its record of the remote generation number is still less than its own ๐Ÿ™…โ€โ™€๏ธ.

Here's a detail to keep in mind: when parts of the datastream get retransmitted, TCP requires implementations to always send the same data bytes for the same TCP sequence numbers ๐Ÿงต. So frame data in retransmitted segments MUST be encrypted with the same key it was originally sent with, no matter what the current local generation number is ๐Ÿ”’.

Implementations SHOULD delete older-generation keys from memory once they've received all the frames they'll need to decrypt with the old keys, and once they've encrypted every outgoing frame under the old keys ๐Ÿ—‘๏ธ.


3.9. Keep-Alive

Rather than using TCP keep-alives to check that the remote endpoint is still responsive, tcpcrypt implementations SHOULD use the rekeying mechanism for this job ๐Ÿ’œ. When it's necessary, a host SHOULD probe its peer's liveness by initiating rekeying and transmitting a new frame right away (with empty application data if necessary) โฐ.

As Section 3.8 described, a host that receives a frame encrypted under a generation number greater than its own MUST increment its own generation number. If it hasn't already transmitted a segment with FIN set, it should immediately transmit a new frame (with zero-length application data if necessary) too ๐Ÿค. Okay wait, I need to be precise here: the original says it MUST increment and then immediately transmit, so that's exactly what happens ๐Ÿ’….

Implementations MAY use TCP keep-alives for purposes that don't require endpoint authentication, as discussed in Section 8.2 ๐Ÿ“.


4. Encodings โœจ

This section gives the byte-level encodings for the values the protocol transmits or computes ๐Ÿงšโ€โ™€๏ธ๐Ÿ’–.

4.1. Key-Exchange Messages

okay so the key-exchange messages are where the handshake gets real ๐Ÿฆ‹ Init1 comes first and it's very structured, bestie ๐Ÿ’…

    byte   0       1       2       3
       +-------+-------+-------+-------+
       |          INIT1_MAGIC          |
       |                               |
       +-------+-------+-------+-------+

               4        5      6       7
           +-------+-------+-------+-------+
           |          message_len          |
           |              = M              |
           +-------+-------+-------+-------+

               8
           +--------+-----+----+-----+----+---...---+-----+-----+
           |nciphers|sym_      |sym_      |         |sym_       |
           | = K    |cipher[0] |cipher[1] |         |cipher[K-1]|
           +--------+-----+----+-----+----+---...---+-----+-----+

            2*K + 9                     2*K + 9 + N_A_LEN
               |                         |
               v                         v
           +-------+---...---+-------+-------+---...---+-------+
           |           N_A           |          Pub_A          |
           |                         |                         |
           +-------+---...---+-------+-------+---...---+-------+

                               M - 1
           +-------+---...---+-------+
           |         ignored         |
           |                         |
           +-------+---...---+-------+

So INIT1_MAGIC is the opener, and it's defined in Section 4.3 โœจ The four-byte message_len field tells you the length of the whole Init1 message, written as a big-endian integer ๐ŸŽ€ Then nciphers says how many two-byte symmetric-cipher identifiers come after it. Those sym_cipher[i] identifiers point at the cryptographic algorithms listed in Table 5 in Section 7 ๐Ÿ’– And the lengths of N_A and Pub_A aren't fixed. They depend on the negotiated TEP, as explained in Section 5 ๐ŸŒธ

Now, implementations of this protocol MUST construct Init1 so that the ignored field has zero length ๐Ÿ˜ญ In other words, MUST construct the message so that its end (as set by message_len) lines up exactly with the end of Pub_A. But when they receive Init1, they MUST permit and ignore any bytes after Pub_A. Build it tight, read it loose ๐Ÿงš

Next up is Init2, the response ๐ŸŒˆ

    byte   0       1       2       3
       +-------+-------+-------+-------+
       |          INIT2_MAGIC          |
       |                               |
       +-------+-------+-------+-------+


               4        5      6       7       8       9
           +-------+-------+-------+-------+-------+-------+
           |          message_len          |  sym_cipher   |
           |              = M              |               |
           +-------+-------+-------+-------+-------+-------+

               10                      10 + N_B_LEN
               |                         |
               v                         v
           +-------+---...---+-------+-------+---...---+-------+
           |           N_B           |          Pub_B          |
           |                         |                         |
           +-------+---...---+-------+-------+---...---+-------+

                               M - 1
           +-------+---...---+-------+
           |          ignored        |
           |                         |
           +-------+---...---+-------+

INIT2_MAGIC is also defined in Section 4.3 ๐Ÿ’œ The four-byte message_len is the length of the entire Init2 message, big-endian, same energy as before. The sym_cipher value is picked from the symmetric-cipher identifiers you got in the earlier Init1 message ๐Ÿ‘‘ And N_B_LEN and Pub_B are sized by the negotiated TEP, per Section 5.

Same rule applies here, bestie ๐Ÿซถ Implementations of this protocol MUST construct Init2 so that the field "ignored" has zero length. That means they MUST construct the message so its end (set by message_len) coincides with the end of the Pub_B field. And when receiving Init2, implementations MUST permit and ignore any bytes following Pub_B.


4.2. Encryption Frames

An encryption frame is basically a control byte plus a length-prefixed ciphertext chunk ๐Ÿ“

    byte   0       1       2       3               clen+2
       +-------+-------+-------+-------+---...---+-------+
       |control|      clen     |        ciphertext       |
       +-------+-------+-------+-------+---...---+-------+

The clen field is a big-endian integer, and it tells you how long the ciphertext field is ๐Ÿฆ‹

The control field has its own little layout:

              bit     7                 1       0
                  +-------+---...---+-------+-------+
                  |          cres           | rekey |
                  +-------+---...---+-------+-------+

The seven-bit cres field is reserved ๐Ÿ™ˆ Implementations MUST set those bits to zero when sending, and MUST ignore them when receiving. Zero on the way out, shrug on the way in. And the rekey field? That's all explained in Section 3.8 ๐Ÿ’…


4.2.1. Plaintext

The ciphertext field is what you get after the negotiated authenticated-encryption algorithm works on a plaintext value ๐Ÿ”ฅ That plaintext comes in one of two shapes:

       byte   0       1               plen-1
          +-------+-------+---...---+-------+
          | flags |           data          |
          +-------+-------+---...---+-------+


       byte   0       1       2       3               plen-1
          +-------+-------+-------+-------+---...---+-------+
          | flags |    urgent     |          data           |
          +-------+-------+-------+-------+---...---+-------+
Quick heads-up ๐Ÿ‘€ clen is usually bigger than plen. That's because the AEAD output both encrypts the application data and adds redundancy so the integrity can be checked. It's giving "extra armor," iykyk โœจ

The flags field is structured like this:

            bit    7    6    5    4    3    2    1    0
                +----+----+----+----+----+----+----+----+
                |            fres             |URGp|FINp|
                +----+----+----+----+----+----+----+----+

The six-bit fres field is reserved too ๐ŸŒธ Implementations MUST set those six bits to zero when sending, and MUST ignore them when receiving. Same vibe as cres, periodt ๐Ÿ’–

If the URGp bit is set, that tells you the urgent field is there, so the plaintext uses the second (longer) format. If it isn't set, you get the first format ๐ŸŽ€ What the urgent field and the flag bits actually mean is covered in Section 3.7 ๐Ÿงš


4.2.2. Associated Data

Every encryption frame has its own associated data ๐Ÿฆ‹ This is the stuff handed to the AEAD algorithm when it decrypts the ciphertext and checks the frame's integrity. It looks like this:

                   byte   0       1       2
                      +-------+-------+-------+
                      |control|     clen      |
                      +-------+-------+-------+

Fun fact (well, it's a fact, so it's fun) ๐ŸŒˆ It contains the very same values as the frame's control and clen fields. Copy-paste energy, but make it cryptographic ๐Ÿ˜ญ


4.2.3. Frame ID

Last but not least, the frame ID is what gets used to build the nonce for the AEAD algorithm ๐Ÿ“

      byte  0            ae_nonce_len - 8    ae_nonce_len - 1
            |                   |             |
            v                   v             v
         +-----+--...--+-----+-----+--...--+-----+
         |  0  |       |  0  |       offset      |
         +-----+--...--+-----+-----+--...--+-----+

The 8-byte offset field is a big-endian integer, and its value is specified in Section 3.6 ๐Ÿ’œ To make the whole thing come out to ae_nonce_len bytes, you prepend zero-valued bytes in front of the offset field. Math is the mood here ๐Ÿ’…


4.3. Constant Values

Here are the constants the protocol uses, all lined up in one neat table ๐Ÿ“‹โœจ

                   +------------+--------------+
                   | Value      | Name         |
                   +------------+--------------+
                   | 0x01       | CONST_NEXTK  |
                   | 0x02       | CONST_SESSID |
                   | 0x03       | CONST_REKEY  |
                   | 0x04       | CONST_KEY_A  |
                   | 0x05       | CONST_KEY_B  |
                   | 0x06       | CONST_RESUME |
                   | 0x15101a0e | INIT1_MAGIC  |
                   | 0x097105e0 | INIT2_MAGIC  |
                   +------------+--------------+

Table 1: Constant Values Used in the Protocol ๐ŸŒŸ

5. Key-Agreement Schemes ๐Ÿ”‘

okay so the TEP you negotiate through TCP-ENO tells you which key-agreement scheme to use from Table 4 in Section 7 ๐ŸŽ€ for example, TCPCRYPT_ECDHE_P256 means tcpcrypt running ECDHE-P256, with the CPRF and length parameters below. iconic naming, honestly ๐Ÿ’…

All the TEPs in this document use HKDF-Expand-SHA256 as the CPRF, and they all use these lengths for nonces and session secrets ๐Ÿฆ‹

N_A_LEN: 32 bytes
N_B_LEN: 32 bytes
K_LEN:   32 bytes

Future documents that assign more TEPs for tcpcrypt might pick different values for those lengths ๐Ÿ‘€ but here's the key bit: the minimum session ID length that TCP-ENO sets, combined with how tcpcrypt builds session IDs, means K_LEN MUST be at least 32 bytes. no skipping that one ๐Ÿ”ฅ

Key-agreement schemes ECDHE-P256 and ECDHE-P521 use the Elliptic Curve Secret Value Derivation Primitive, Diffie-Hellman version (ECSVDP-DH) from [IEEE-1363] ๐ŸŒธ the named curves come from [NIST-DSS]. When the public-key values Pub_A and Pub_B get sent as described in Section 4.1, they're encoded with the "Elliptic Curve Point to Octet String Conversion Primitive" from Section E.2.3 of [IEEE-1363] and get a two-byte length prefix in big-endian format ๐Ÿงš

   byte   0       1       2               L - 1
      +-------+-------+-------+---...---+-------+
      |   pubkey_len  |          pubkey         |
      |      = L      |                         |
      +-------+-------+-------+---...---+-------+

Implementations MUST encode these pubkey values in "compressed format" โœจ and implementations MUST validate these pubkey values using the algorithm in Section A.16.10 of [IEEE-1363]. non-negotiable, bestie ๐Ÿซถ

Key-agreement schemes ECDHE-Curve25519 and ECDHE-Curve448 do Diffie-Hellman with the functions X25519 and X448, respectively ๐ŸŒˆ implementations SHOULD compute these functions with the algorithms in [RFC7748]. When they do, implementations MUST check whether the computed Diffie-Hellman shared secret is the all-zero value, and abort if it is, as described in Section 6 of [RFC7748] ๐Ÿ’€ alternative implementations of these functions SHOULD abort when either input forces the shared secret into one of a small set of values, as discussed in Section 7 of [RFC7748].

For these schemes, Pub_A and Pub_B are sent directly with no length prefix: 32 bytes for ECDHE-Curve25519 and 56 bytes for ECDHE-Curve448 ๐Ÿ“

Table 2 below lays out the requirement levels for the four TEPs in this document ๐Ÿ“‹ the big one: all implementations of tcpcrypt MUST support TCPCRYPT_ECDHE_Curve25519 ๐ŸŽ€ system administrators MAY configure which TEPs a host negotiates, separate from these implementation requirements ๐Ÿ™ˆ

+-------------+---------------------------+
| Requirement | TEP                       |
+-------------+---------------------------+
| REQUIRED    | TCPCRYPT_ECDHE_Curve25519 |
| RECOMMENDED | TCPCRYPT_ECDHE_Curve448   |
| OPTIONAL    | TCPCRYPT_ECDHE_P256       |
| OPTIONAL    | TCPCRYPT_ECDHE_P521       |
+-------------+---------------------------+

Table 2: Requirements for Implementation of TEPs


6. AEAD Algorithms ๐Ÿง

This document uses sym_cipher identifiers in the Init1 and Init2 messages (see Section 3.3) to negotiate AEAD algorithms ๐Ÿ’– the values for those identifiers are in Table 5 in Section 7. AEAD_AES_128_GCM and AEAD_AES_256_GCM are specified in [RFC5116], and AEAD_CHACHA20_POLY1305 is specified in [RFC8439] ๐Ÿฆ‹

Implementations MUST support certain AEAD algorithms, per Table 3 ๐Ÿ”ฅ system administrators MAY configure which algorithms a host negotiates, independent of these requirements ๐Ÿ‘‘

Last thing: this document uses the lengths ae_key_len and ae_nonce_len to describe aspects of encryption and data formats ๐Ÿซง those values depend on the negotiated AEAD algorithm, so check the table below ๐ŸŒธ

+------------------------+-------------+------------+--------------+
| AEAD Algorithm         | Requirement | ae_key_len | ae_nonce_len |
+------------------------+-------------+------------+--------------+
| AEAD_AES_128_GCM       | REQUIRED    | 16 bytes   | 12 bytes     |
| AEAD_AES_256_GCM       | RECOMMENDED | 32 bytes   | 12 bytes     |
| AEAD_CHACHA20_POLY1305 | RECOMMENDED | 32 bytes   | 12 bytes     |
+------------------------+-------------+------------+--------------+

Table 3: Requirement and Lengths for Each AEAD Algorithm


7. IANA Considerations ๐Ÿ—‚๏ธ

For TCP-ENO's negotiation mechanism, tcpcrypt's TEP identifiers have been added to IANA's "TCP Encryption Protocol Identifiers" registry, which lives under the "Transmission Control Protocol (TCP) Parameters" registry ๐ŸŽ€ see Table 4. The key-agreement schemes these tcpcrypt variants use are defined in Section 5 โœจ

+-------+---------------------------+-----------+
| Value | Meaning                   | Reference |
+-------+---------------------------+-----------+
| 0x21  | TCPCRYPT_ECDHE_P256       | [RFC8548] |
| 0x22  | TCPCRYPT_ECDHE_P521       | [RFC8548] |
| 0x23  | TCPCRYPT_ECDHE_Curve25519 | [RFC8548] |
| 0x24  | TCPCRYPT_ECDHE_Curve448   | [RFC8548] |
+-------+---------------------------+-----------+

Table 4: TEP Identifiers for Use with tcpcrypt

In Section 6, this document defines several AEAD algorithms for encrypting application data ๐Ÿ” to name them, tcpcrypt uses two-byte identifiers in the range 0x0001 to 0xFFFF, inclusive. IANA maintains a new "tcpcrypt AEAD Algorithms" registry for these, under the "Transmission Control Protocol (TCP) Parameters" registry ๐Ÿ’œ the initial values are in Table 5. Future assignments have to satisfy one of two policies from [RFC8126]: "IETF Review" or, for non-IETF stream specifications, "Expert Review with RFC Required." IANA will also do early allocation [RFC7120] so testing can happen before RFCs are finalized ๐Ÿงš

+--------+------------------------+----------------------+
| Value  | AEAD Algorithm         | Reference            |
+--------+------------------------+----------------------+
| 0x0001 | AEAD_AES_128_GCM       | [RFC8548], Section 6 |
| 0x0002 | AEAD_AES_256_GCM       | [RFC8548], Section 6 |
| 0x0010 | AEAD_CHACHA20_POLY1305 | [RFC8548], Section 6 |
+--------+------------------------+----------------------+

Table 5: Authenticated-Encryption Algorithms for Use with tcpcrypt


8. Security Considerations ๐Ÿšจ

Okay this section is SERIOUS bestie ๐Ÿ˜ญ every security consideration of TCP-ENO applies to tcpcrypt too. Most importantly, tcpcrypt does NOT protect against active network attackers unless applications authenticate the session ID ๐Ÿ’€ if the session IDs computed at each end match, tcpcrypt guarantees there were no man-in-the-middle attacks, UNLESS the attacker broke the underlying crypto primitives, like Elliptic Curve Diffie-Hellman (ECDH). A proof of this property for an earlier version of the protocol has been published [tcpcrypt] ๐Ÿ“š

To keep middleboxes happy, tcpcrypt does not protect TCP headers ๐Ÿ™ˆ that means it's open to denial-of-service from off-path attackers, just like plain TCP. Possible attacks include desynchronizing the underlying TCP stream, injecting RST or FIN segments, and forging rekey bits. These attacks make a tcpcrypt connection hang or fail with an error, but never in a situation where plain TCP could have kept going uncorrupted ๐Ÿ˜ค implementations MUST give higher-level software a way to tell these errors apart from a clean end-of-stream (indicated by an authenticated FINp bit), so applications can avoid semantic truncation attacks. this part is a must-have ๐Ÿซถ

There is no "key confirmation" step in tcpcrypt. This is not needed because tcpcrypt's threat model already includes the possibility of a connection to an adversary. If key negotiation is compromised and yields two different keys, failed integrity checks on every subsequent frame will make the connection either hang or abort. This is not a new threat, since an active attacker can get the same results against a plain TCP connection by injecting RST segments or modifying sequence and acknowledgement numbers.

tcpcrypt uses short-lived public keys for forward secrecy ๐ŸงŠ once an implementation removes these keys from memory, a compromise of the system can't be used to derive the session secrets for past connections. All currently-specified key-agreement schemes use Ephemeral Elliptic Curve Diffie-Hellman (ECDHE), which means a new key pair can be computed efficiently for each connection โœจ if implementations reuse these parameters, they MUST limit the lifetime of the private parameters as far as is practical, so fewer past connections are vulnerable ๐Ÿ”ฅ placing private keys in persistent storage is a huge risk, since they might not get destroyed reliably and in a timely way, so that SHOULD be avoided whenever possible ๐Ÿ’€

Attackers can't force passive openers to move forward in their session resumption chain without guessing the content of the resumption identifier, which is hard without key knowledge ๐Ÿ”

The cipher-suites in this document all use HMAC-SHA256 to implement the collision-resistant pseudo-random function called CPRF ๐Ÿ’– a collision-resistant function is one where, for a sufficiently large L, an attacker can't find two distinct inputs (K_1, CONST_1) and (K_2, CONST_2) such that CPRF(K_1, CONST_1, L) = CPRF(K_2, CONST_2, L). Collision resistance matters for keeping session IDs unique, since those are generated with the CPRF ๐ŸŽ€

Lastly, a lot of tcpcrypt's crypto functions need random input, so any host implementing tcpcrypt MUST have access to a cryptographically-secure source of randomness or pseudo-randomness ๐ŸŽฒ [RFC4086] has recommendations on how to get that ๐Ÿ“–

Most implementations will rely on a device's pseudo-random generator, seeded from hardware events and a seed carried over from the previous boot ๐Ÿ–ฅ๏ธ once a pseudo-random generator is properly seeded, it can generate effectively arbitrary amounts of pseudo-random data โœจ but until it's seeded with sufficient entropy, tcpcrypt isn't just insecure, it will also leak information that further weakens the pseudo-random generator, potentially harming other applications ๐Ÿ˜ฑ As REQUIRED by TCP-ENO, implementations MUST NOT send ENO options unless they have access to an adequate source of randomness ๐Ÿšซ๐ŸŽฒ

8.1. Asymmetric Roles ๐ŸŽ€

okay so tcpcrypt takes one shared pseudo-random key (PRK) and turns it into cryptographic traffic keys, one for each direction ๐Ÿ”‘. To pull that off, the protocol needs an asymmetry, because the key derivation function has to be perturbed differently to make a different key for each direction. ๐Ÿง  The two hosts also aren't identical in other ways. For example, one side proposes cipher suites while the other side selects one, so the roles are lowkey different ๐Ÿ’…

8.2. Verified Liveness ๐Ÿฆ‹

so a lot of hosts support TCP keep-alives [RFC1122] as an option, so applications can check that the other end of a TCP connection is still around even when nobody's sending data ๐Ÿ‘€ A TCP keep-alive segment carries a sequence number one prior to the beginning of the send window and may carry one byte of "garbage" data. That segment makes the remote side send back an acknowledgment, and that's the whole trick.

BUT here's the tea ๐Ÿ˜ญ tcpcrypt cannot cryptographically verify keep-alive acknowledgments. That means an attacker could keep a session alive on one host long after the other end is gone. ๐Ÿ’€ That might stop a process holding sensitive data from exiting, which gives an attacker more time to compromise the host and grab that data. Not okay!

To shut that down, tcpcrypt has a way to poke the remote host into sending data that is verifiably fresh and authentic, which is described in Section 3.9. โœจ

Also, the TCP keep-alive mechanism has been used for its effects on middleboxes, like keeping flow state from expiring at NAT boxes or firewalls ๐Ÿงš Those jobs don't need endpoint authentication, so implementations MAY safely use either the existing TCP keep-alive mechanism or tcpcrypt's verified keep-alive mechanism for them. Pick your fighter ๐ŸŒธ

8.3. Mandatory Key-Agreement Schemes ๐Ÿ’œ

this document says tcpcrypt implementations have to support at least one key-agreement scheme: ECDHE using Curve25519 ๐ŸŒˆ Picking just one mandatory algorithm was a seriously hard tradeoff between cryptographic diversity and how easy and secure it is to actually deploy.

The IETF's current best practice on this [RFC7696] says, "Ideally, two independent sets of mandatory-to-implement algorithms will be specified, allowing for a primary suite and a secondary suite. This approach ensures that the secondary suite is widely deployed if a flaw is found in the primary one."

So you'd think we'd also mandate ECDHE using P-256 to hit that ideal, right? Not so fast ๐Ÿ™…โ€โ™€๏ธ Implementing the Diffie-Hellman function with NIST elliptic curves (including P-256 and P-521, the ones specified for tcpcrypt) seems really hard to do without opening up side-channel attacks [NIST-fail]. Good trusted implementations do exist inside big crypto libraries, but those are a pain to pull into operating-system kernels, which is usually where tcpcrypt gets implemented. Curve25519 is the opposite vibe: its characteristics plus its recent popularity have produced lots of safe, efficient implementations, some of which fit right into the kernel ๐Ÿซถ

[RFC7696] also insists that, "The selected algorithms need to be resistant to side-channel attacks and also meet the performance, power, and code size requirements on a wide variety of platforms." Following that principle, tcpcrypt leaves the NIST curves out of the mandatory-to-implement key-agreement set. ๐Ÿ™…โ€โ™€๏ธ

Lastly, this document encourages key agreement with Curve448, and it's categorized as RECOMMENDED ๐Ÿ“ Curve448 looks likely to allow safe and efficient implementations. Support isn't REQUIRED though, because existing implementations might not be well proven yet.


9. Experiments ๐Ÿงช

some experience is going to be needed to figure out whether the tcpcrypt protocol can be deployed safely and successfully across all the wildly different corners of the global internet ๐ŸŒ

Safety means TCP implementations that support tcpcrypt can still talk reliably in every setting where they'd work without tcpcrypt. As described in Section 9 of [RFC8547], this can get broken if middleboxes strip ENO options from non-SYN segments after letting them through in SYN segments, or if tcpcrypt's communication patterns annoy the policies of middleboxes doing deep-packet inspection. ๐Ÿ‘€

Success, on top of safety, means hosts that implement tcpcrypt actually turn on encryption when they connect to each other โœจ That depends on how the network treats the TCP-ENO handshake, and it can get sabotaged if middleboxes just strip unknown TCP options, or if they terminate TCP connections and relay data back and forth unencrypted. Yikes ๐Ÿ˜ญ

Ease of implementation is another challenge. Since tcpcrypt needs encryption operations on frames that may span TCP segments, kernel implementations have to buffer segments in different ways than plain TCP does. ๐Ÿงฉ More implementation experience will reveal how much extra code complexity shows up across operating systems, and what kind of performance effects to expect.


10. References ๐Ÿ“š

10.1. Normative References

[IEEE-1363]
           IEEE, "IEEE Standard Specifications for Public-Key
           Cryptography", IEEE Standard 1363-2000,
           DOI 10.1109/IEEESTD.2000.92292.

[NIST-DSS] National Institute of Standards and Technology (NIST),
           "Digital Signature Standard (DSS)", FIPS PUB 186-4,
           DOI 10.6028/NIST.FIPS.186-4, July 2013.

[RFC793]   Postel, J., "Transmission Control Protocol", STD 7,
           RFC 793, DOI 10.17487/RFC0793, September 1981,
           <https://www.rfc-editor.org/info/rfc793>.

[RFC2018]  Mathis, M., Mahdavi, J., Floyd, S., and A. Romanow, "TCP
           Selective Acknowledgment Options", RFC 2018,
           DOI 10.17487/RFC2018, October 1996,
           <https://www.rfc-editor.org/info/rfc2018>.

[RFC2104]  Krawczyk, H., Bellare, M., and R. Canetti, "HMAC: Keyed-
           Hashing for Message Authentication", RFC 2104,
           DOI 10.17487/RFC2104, February 1997,
           <https://www.rfc-editor.org/info/rfc2104>.

[RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
           Requirement Levels", BCP 14, RFC 2119,
           DOI 10.17487/RFC2119, March 1997,
           <https://www.rfc-editor.org/info/rfc2119>.

[RFC5116]  McGrew, D., "An Interface and Algorithms for Authenticated
           Encryption", RFC 5116, DOI 10.17487/RFC5116, January 2008,
           <https://www.rfc-editor.org/info/rfc5116>.

[RFC5869]  Krawczyk, H. and P. Eronen, "HMAC-based Extract-and-Expand
           Key Derivation Function (HKDF)", RFC 5869,
           DOI 10.17487/RFC5869, May 2010,
           <https://www.rfc-editor.org/info/rfc5869>.

[RFC7120]  Cotton, M., "Early IANA Allocation of Standards Track Code
           Points", BCP 100, RFC 7120, DOI 10.17487/RFC7120, January
           2014, <https://www.rfc-editor.org/info/rfc7120>.

[RFC7748]  Langley, A., Hamburg, M., and S. Turner, "Elliptic Curves
           for Security", RFC 7748, DOI 10.17487/RFC7748, January
           2016, <https://www.rfc-editor.org/info/rfc7748>.

[RFC8126]  Cotton, M., Leiba, B., and T. Narten, "Guidelines for
           Writing an IANA Considerations Section in RFCs", BCP 26,
           RFC 8126, DOI 10.17487/RFC8126, June 2017,
           <https://www.rfc-editor.org/info/rfc8126>.

[RFC8174]  Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC
           2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174,
           May 2017, <https://www.rfc-editor.org/info/rfc8174>.

[RFC8439]  Nir, Y. and A. Langley, "ChaCha20 and Poly1305 for IETF
           Protocols", RFC 8439, DOI 10.17487/RFC8439, June 2018,
           <https://www.rfc-editor.org/info/rfc8439>.

[RFC8547]  Bittau, A., Giffin, D., Handley, M., Mazieres, D., and
           E. Smith, "TCP-ENO: Encryption Negotiation Option",
           RFC 8547, DOI 10.17487/RFC8547, May 2019,
           <https://www.rfc-editor.org/info/rfc8547>.

10.2. Informative References

[NIST-fail]
           Bernstein, D. and T. Lange, "Failures in NIST's ECC
           Standards", January 2016,
           <https://cr.yp.to/newelliptic/nistecc-20160106.pdf>.

[RFC1122]  Braden, R., Ed., "Requirements for Internet Hosts -
           Communication Layers", STD 3, RFC 1122,
           DOI 10.17487/RFC1122, October 1989,
           <https://www.rfc-editor.org/info/rfc1122>.

[RFC4086]  Eastlake 3rd, D., Schiller, J., and S. Crocker,
           "Randomness Requirements for Security", BCP 106, RFC 4086,
           DOI 10.17487/RFC4086, June 2005,
           <https://www.rfc-editor.org/info/rfc4086>.

[RFC7696]  Housley, R., "Guidelines for Cryptographic Algorithm
           Agility and Selecting Mandatory-to-Implement Algorithms",
           BCP 201, RFC 7696, DOI 10.17487/RFC7696, November 2015,
           <https://www.rfc-editor.org/info/rfc7696>.

[tcpcrypt] Bittau, A., Hamburg, M., Handley, M., Mazieres, D., and
           D. Boneh, "The case for ubiquitous transport-level
           encryption", USENIX Security Symposium, August 2010.

[TCPINC-API]
           Bittau, A., Boneh, D., Giffin, D., Handley, M., Mazieres,
           D., and E. Smith, "Interface Extensions for TCP-ENO and
           tcpcrypt", Work in Progress, draft-ietf-tcpinc-api-06,
           June 2018.

Acknowledgments ๐Ÿซถ

huge thank yous to the TCPINC Working Group and to the other IETF reviewers whose contributions, help, discussions, and feedback made this possible ๐Ÿ’– That includes Marcelo Bagnulo, David Black, Bob Briscoe, Jana Iyengar, Stephen Kent, Tero Kivinen, Mirja Kuhlewind, Yoav Nir, Christoph Paasch, Eric Rescorla, Kyle Rose, and Dale Worley. Iconic squad ๐Ÿ‘‘

This work was funded by gifts from Intel (to Brad Karp) and from Google, plus NSF award CNS-0716806 (A Clean-Slate Infrastructure for Information Flow Control), DARPA CRASH under contract #N66001-10-2-4088, and the Stanford Secure Internet of Things Project. Money well spent, periodt ๐Ÿ’ธโœจ

Contributors ๐ŸŒธ

Dan Boneh and Michael Hamburg were coauthors of the draft that became this document. Literal co-creators, no notes ๐Ÿงš

Authors' Addresses ๐Ÿฆ‹

Andrea Bittau
Google
345 Spear Street
San Francisco, CA  94105
United States of America

Email: bittau@google.com

Daniel B. Giffin
Stanford University
353 Serra Mall, Room 288
Stanford, CA  94305
United States of America

Email: daniel@beech-grove.net

Mark Handley
University College London
Gower St.
London  WC1E 6BT
United Kingdom

Email: M.Handley@cs.ucl.ac.uk

David Mazieres
Stanford University
353 Serra Mall, Room 290
Stanford, CA  94305
United States of America

Email: dm@uun.org

Quinn Slack
Sourcegraph
121 2nd St Ste 200
San Francisco, CA  94105
United States of America

Email: sqs@sourcegraph.com

Eric W. Smith
Kestrel Institute
3260 Hillview Avenue
Palo Alto, CA  94304
United States of America

Email: eric.smith@kestrel.edu
๐Ÿ“ฆ where this page comes from