Julien00859 / siotls

sans-io TLS 1.3 protocol stack
MIT License
2 stars 0 forks source link

Maximum frame length in regard to the extra record header / aead tag bytes #8

Open Julien00859 opened 9 months ago

Julien00859 commented 9 months ago

Plaintext TLS 1.3 records (what's written on the wire):

 <------plaintext header--------> <-plaintext fragment->
| content-type | fragment-length |       message        |
 <-----1B-----> <------3B-------> <-------16kiB-------->

The 16kiB limit of the plaintext fragment is defined in the RFC^1:

length: The length (in bytes) of the following TLSPlaintext.fragment. The length MUST NOT exceed 2^14 bytes.

Encrypted TLS 1.3 records:

 <--------plaintext header-----------> <-------------encrypted fragment------------->
| fake-content-type | fragment-length | message | content-type | padding  | aead-tag |
 <-------1B--------> <------3B-------> <-16kiB-> <-----1B-----> <variable> <--255B-->

The 16kiB limit of the encrypted message, along with the 256 extra bytes (content-type, aead tag) are also defined in the RFC^1:

length: The length (in bytes) of the following TLSCiphertext.encrypted_record, which is the sum of the lengths of the content and the padding, plus one for the inner content type, plus any expansion added by the AEAD algorithm. The length MUST NOT exceed 2^14 + 256 bytes.

What is the relation of those 256 extra bytes in regard to the Max Fragment Length extension ?

When you decrypt, do you need to add the extra 256 bytes only when the max fragment length is 16kiB or always? Like if the client requests a maximum fragment length of 1024 bytes via the MFL extension, should it reads 1024 encrypted bytes (larger records) or 1024+256 encrypted bytes (larger message)?

Also when generating records, what record size and message size shall we aim? Shall we aim at records, fragments or messages of MFL/16kiB bytes? What's about the 5 bytes record header, the 1 byte encrypted content type and the 255 bytes AEAD tag?

The RFC hints at generating plaintext fragments (i.e. messages) of 16kiB bytes, meaning that we actually write 4 + 16ki + 1 + 255 bytes on the wire: 16644 bytes. If it is right, we should say to users: "you should do socket.read(16644), that is 16kiB for the encrypted messages and 260 extra bytes added by TLS". Again, what's about the Max Fragment Length extension, 260 extra bytes is a pretty big deal when you request MFL of 1024 bytes. Is it really 1024+260 bytes on the wire (messages of 1024 bytes) or is it 1024 bytes on the write (messages of 764 bytes)?

That's for receiving end but what's about the messages siotls generates? We can decide to encrypt shorter messages so that in the end the actual records are exactly MFL/16ki bytes. That would be easy to document: "siotls always generate records of MFL/16ki bytes)". But we might not do like everybody else is doing and maybe its better to stick with a de-facto standard (understand: what openssl does). Maybe we are genius and the routers and switches in the wild do have strictly 16kiB buffers and not 16ki+260 bytes one hence we were to generate records of 16kiB (instead of messages of 16kiB) we would gain in performances in the real world.

We should also keep in mind that TLS is used to encrypt other protocols and that those other protocols have their own framing protocol and own expectation, e.g. http2^2

The size of a frame payload is limited by the maximum size that a receiver advertises in the SETTINGS_MAX_FRAME_SIZE setting. This setting can have any value between 2^(14) (16,384) and 2^(24)-1 (16,777,215) octets, inclusive.

People might generate 16kiB http2 messages and expect them to be sent at once on the wire, if we were to generate 16ki-261B messages, we would encode a single http2 message in two TLS records, this might be sub-optimal.

Julien00859 commented 9 months ago

Your message seems to say that the cipher algorithms can inflate the fragment length by 255 bytes, this is actually not very precise. In TLS 1.2 and earlier, some supported ciphers (e.g. AES-CBC) required that the input message about to be encrypted had a size that was a multiple of their block size. When not, the input message was padded so that the total length reached a block-size multiple.

Such padding is actually not needed by any of the RFC8446 specified cipher (aes-gcm, aes-ccm, cha-poly) which all are AEAD. I don't know if all AEAD algorithms are padding-less or if it is only those 3 ones but I would be surprised to see a TLS compatible AEAD algorithm requiring padding, it would say that the message length could be inflated by 255 (padding) + 16 (tag) bytes which would go against this TLS requirements:

An AEAD algorithm used in TLS 1.3 MUST NOT produce an expansion greater than 255 octets.^1

So at the moment, the supported aead ciphers only expand the message by 16 bytes (8 for aes-ccm-8), but the RFC says that expansions up to 255 are permitted.

Note that this does not answer any of my questions...

Julien00859 commented 8 months ago

The answer might resides in the description of the RecordOverflow alert