Aller au contenu principal

5. Record Protocol (Protocole d'enregistrement)

Le protocole d'enregistrement TLS prend les messages à transmettre, fragmente les données en blocs gérables, protège les enregistrements et transmet le résultat. Les données reçues sont vérifiées, déchiffrées, réassemblées, puis livrées aux clients de niveau supérieur.

Les enregistrements TLS sont typés, ce qui permet de multiplexer plusieurs protocoles de niveau supérieur sur la même couche d'enregistrement. Ce document spécifie quatre types de contenu : handshake, application_data, alert et change_cipher_spec. L'enregistrement change_cipher_spec n'est utilisé qu'à des fins de compatibilité (voir l'annexe D.4).

Une implémentation peut (MAY) recevoir un enregistrement non chiffré de type change_cipher_spec composé de la valeur d'octet unique 0x01 à tout moment après l'envoi ou la réception du premier message ClientHello et avant la réception du message Finished du pair, et doit (MUST) simplement le rejeter sans traitement supplémentaire. Notez que cet enregistrement peut apparaître à un point de la poignée de main où l'implémentation attend des enregistrements protégés, de sorte qu'il est nécessaire de détecter cette condition avant de tenter de déprotéger l'enregistrement. Les implémentations qui reçoivent une autre valeur change_cipher_spec ou qui reçoivent un enregistrement change_cipher_spec protégé doivent (MUST) abandonner la poignée de main avec une alerte "unexpected_message". Si une implémentation détecte un enregistrement change_cipher_spec reçu avant le premier message ClientHello ou après le message Finished du pair, il doit (MUST) être traité comme un type d'enregistrement inattendu (bien que les serveurs sans état puissent ne pas être en mesure de distinguer ces cas des cas autorisés).

Les implémentations ne doivent pas (MUST NOT) envoyer de types d'enregistrement non définis dans ce document, sauf s'ils sont négociés par une extension. Si une implémentation TLS reçoit un type d'enregistrement inattendu, elle doit (MUST) terminer la connexion avec une alerte "unexpected_message". De nouvelles valeurs de type de contenu d'enregistrement sont attribuées par l'IANA dans le registre TLS ContentType, comme décrit à la section 11.

5.1. Record Layer (Couche d'enregistrement)​

La couche d'enregistrement fragmente les blocs d'informations en enregistrements TLSPlaintext transportant des données en morceaux de 2^14 octets ou moins. Les limites des messages sont gérées différemment selon le ContentType sous-jacent. Tout type de contenu futur doit (MUST) spécifier des règles appropriées. Notez que ces règles sont plus strictes que celles appliquées dans TLS 1.2.

Les messages de handshake peuvent (MAY) être fusionnés en un seul enregistrement TLSPlaintext ou fragmentés sur plusieurs enregistrements, à condition que :

  • Les messages de handshake ne doivent pas (MUST NOT) être entrelacés avec d'autres types d'enregistrements. Autrement dit, si un message de handshake est réparti sur deux enregistrements ou plus, il ne doit pas (MUST NOT) y avoir d'autre enregistrement entre eux.

  • Les messages de handshake ne doivent pas (MUST NOT) s'étendre sur les changements de clé. Les implémentations doivent (MUST) vérifier que tous les messages précédant immédiatement un changement de clé sont alignés sur une limite d'enregistrement ; dans le cas contraire, elles doivent (MUST) terminer la connexion avec une alerte "unexpected_message". Étant donné que les messages ClientHello, EndOfEarlyData, ServerHello, Finished et KeyUpdate peuvent précéder immédiatement un changement de clé, les implémentations doivent (MUST) envoyer ces messages en alignement avec une limite d'enregistrement.

Les implémentations ne doivent pas (MUST NOT) envoyer de fragments de longueur zéro des types Handshake, même si ces fragments contiennent du rembourrage.

Les messages d'alerte (section 6) ne doivent pas (MUST NOT) être fragmentés sur plusieurs enregistrements, et plusieurs messages d'alerte ne doivent pas (MUST NOT) être fusionnés en un seul enregistrement TLSPlaintext. En d'autres termes, un enregistrement de type Alert doit (MUST) contenir exactement un message.

Les messages Application Data contiennent des données opaques pour TLS. Les messages Application Data sont toujours protégés. Des fragments de longueur zéro d'Application Data peuvent (MAY) être envoyés, car ils sont potentiellement utiles comme contre-mesure contre l'analyse de trafic. Les fragments d'Application Data peuvent (MAY) être répartis sur plusieurs enregistrements ou fusionnés en un seul enregistrement.

enum {
invalid(0),
change_cipher_spec(20),
alert(21),
handshake(22),
application_data(23),
(255)
} ContentType;

struct {
ContentType type;
ProtocolVersion legacy_record_version;
uint16 length;
opaque fragment[TLSPlaintext.length];
} TLSPlaintext;

type : le protocole de niveau supérieur utilisé pour traiter le fragment encapsulé.

legacy_record_version : doit (MUST) être défini à 0x0303 pour tous les enregistrements générés par une implémentation de TLS 1.3 autres qu'un ClientHello initial (c'est-à-dire un ClientHello non généré après un HelloRetryRequest), où il peut (MAY) également valoir 0x0301 à des fins de compatibilité. Ce champ est obsolète et doit (MUST) être ignoré à toutes fins. Les versions précédentes de TLS utilisaient d'autres valeurs dans ce champ dans certaines circonstances.

length : la longueur (en octets) du TLSPlaintext.fragment suivant. La longueur ne doit pas (MUST NOT) dépasser 2^14 octets. Un point de terminaison qui reçoit un enregistrement dépassant cette longueur doit (MUST) terminer la connexion avec une alerte "record_overflow".

fragment : les données transmises. Cette valeur est transparente et est traitée comme un bloc indépendant à traiter par le protocole de niveau supérieur spécifié par le champ type.

Ce document décrit TLS 1.3, qui utilise la version 0x0304. Cette valeur de version est historique, dérivant de l'utilisation de 0x0301 pour TLS 1.0 et de 0x0300 pour SSL 3.0. Afin de maximiser la compatibilité ascendante, un enregistrement contenant un ClientHello initial devrait (SHOULD) avoir la version 0x0301 (reflétant TLS 1.0) et un enregistrement contenant un second ClientHello ou un ServerHello doit (MUST) avoir la version 0x0303 (reflétant TLS 1.2). Lors de la négociation de versions antérieures de TLS, les points de terminaison suivent la procédure et les exigences fournies à l'annexe D.

Lorsque la protection des enregistrements n'a pas encore été engagée, les structures TLSPlaintext sont écrites directement sur le support. Une fois la protection des enregistrements commencée, les enregistrements TLSPlaintext sont protégés et envoyés comme décrit dans la section suivante. Notez que les enregistrements Application Data ne doivent pas (MUST NOT) être écrits sur le support sans protection (voir la section 2 pour plus de détails).

5.2. Record Payload Protection (Protection de la charge utile d'enregistrement)​

Les fonctions de protection d'enregistrement traduisent une structure TLSPlaintext en structure TLSCiphertext. Les fonctions de déprotection inversent le processus. Dans TLS 1.3, contrairement aux versions précédentes de TLS, tous les chiffrements sont modélisés comme "Authenticated Encryption with Associated Data" (AEAD) [RFC5116]. Les fonctions AEAD fournissent une opération unifiée de chiffrement et d'authentification qui transforme le texte en clair en texte chiffré authentifié et inversement. Chaque enregistrement chiffré se compose d'un en-tête en clair suivi d'un corps chiffré, qui contient lui-même un type et un rembourrage facultatif.

struct {
opaque content[TLSPlaintext.length];
ContentType type;
uint8 zeros[length_of_padding];
} TLSInnerPlaintext;

struct {
ContentType opaque_type = application_data; /* 23 */
ProtocolVersion legacy_record_version = 0x0303; /* TLS v1.2 */
uint16 length;
opaque encrypted_record[TLSCiphertext.length];
} TLSCiphertext;

content : la valeur TLSPlaintext.fragment, contenant l'encodage en octets d'un message de handshake ou d'alerte, ou les octets bruts des données de l'application à envoyer.

type : la valeur TLSPlaintext.type contenant le type de contenu de l'enregistrement.

zeros : une suite d'octets de valeur nulle de longueur arbitraire peut (MAY) apparaître dans le texte en clair après le champ type. Cela offre aux expéditeurs la possibilité de rembourrer tout enregistrement TLS d'une quantité choisie tant que le total reste dans les limites de taille d'enregistrement. Voir la section 5.4 pour plus de détails.

opaque_type : le champ opaque_type externe d'un enregistrement TLSCiphertext est toujours défini à la valeur 23 (application_data) pour une compatibilité vers l'extérieur avec les middlebox habitués à analyser les versions précédentes de TLS. Le type de contenu réel de l'enregistrement se trouve dans TLSInnerPlaintext.type après déchiffrement.

legacy_record_version : le champ legacy_record_version vaut toujours 0x0303. Les TLSCiphertext de TLS 1.3 ne sont pas générés avant que TLS 1.3 n'ait été négocié, il n'y a donc aucune préoccupation de compatibilité historique où d'autres valeurs pourraient être reçues. Notez que le protocole de handshake, y compris les messages ClientHello et ServerHello, authentifie la version du protocole, de sorte que cette valeur est redondante.

length : la longueur (en octets) du TLSCiphertext.encrypted_record suivant, qui est la somme des longueurs du contenu et du rembourrage, plus un pour le type de contenu interne, plus toute expansion ajoutée par l'algorithme AEAD. La longueur ne doit pas (MUST NOT) dépasser 2^14 + 256 octets. Un point de terminaison qui reçoit un enregistrement dépassant cette longueur doit (MUST) terminer la connexion avec une alerte "record_overflow".

encrypted_record : la forme chiffrée par AEAD de la structure TLSInnerPlaintext sérialisée.

Les algorithmes AEAD prennent en entrée une clé unique, un nonce, un texte en clair et des "données supplémentaires" à inclure dans la vérification d'authentification, comme décrit à la section 2.1 de [RFC5116]. La clé est soit client_write_key, soit server_write_key, le nonce est dérivé du numéro de séquence et de client_write_iv ou server_write_iv (voir la section 5.3), et l'entrée de données supplémentaires est l'en-tête de l'enregistrement.

C'est-à-dire,

additional_data = TLSCiphertext.opaque_type ||
TLSCiphertext.legacy_record_version ||
TLSCiphertext.length

L'entrée en clair de l'algorithme AEAD est la structure TLSInnerPlaintext encodée. La dérivation des clés de trafic est définie à la section 7.3.

La sortie AEAD consiste en la sortie de texte chiffré de l'opération de chiffrement AEAD. La longueur du texte en clair est supérieure au TLSPlaintext.length correspondant en raison de l'inclusion de TLSInnerPlaintext.type et de tout rembourrage fourni par l'expéditeur. La longueur de la sortie AEAD sera généralement supérieure à celle du texte en clair, mais d'une quantité qui varie selon l'algorithme AEAD. Comme les chiffrements peuvent incorporer du rembourrage, la quantité de surcoût peut varier selon les différentes longueurs de texte en clair. Symboliquement,

AEADEncrypted =
AEAD-Encrypt(write_key, nonce, additional_data, plaintext)

Le champ encrypted_record de TLSCiphertext est défini à AEADEncrypted.

Pour déchiffrer et vérifier, le chiffrement prend en entrée la clé, le nonce, les données supplémentaires et la valeur AEADEncrypted. La sortie est soit le texte en clair, soit une erreur indiquant que le déchiffrement a échoué. Il n'y a pas de contrôle d'intégrité séparé. Symboliquement,

plaintext of encrypted_record =
AEAD-Decrypt(peer_write_key, nonce,
additional_data, AEADEncrypted)

Si le déchiffrement échoue, le destinataire doit (MUST) terminer la connexion avec une alerte "bad_record_mac".

Un algorithme AEAD utilisé dans TLS 1.3 ne doit pas (MUST NOT) produire une expansion supérieure à 255 octets. Un point de terminaison qui reçoit de son pair un enregistrement dont le TLSCiphertext.length est supérieur à 2^14 + 256 octets doit (MUST) terminer la connexion avec une alerte "record_overflow". Cette limite est dérivée de la longueur maximale de TLSInnerPlaintext de 2^14 octets + 1 octet pour ContentType + l'expansion AEAD maximale de 255 octets.

5.3. Per-Record Nonce (Nonce par enregistrement)​

Un numéro de séquence de 64 bits est maintenu séparément pour la lecture et l'écriture des enregistrements. Le numéro de séquence approprié est incrémenté de un après la lecture ou l'écriture de chaque enregistrement. Chaque numéro de séquence est mis à zéro au début d'une connexion et chaque fois que la clé est changée ; le premier enregistrement transmis sous une clé de trafic particulière doit (MUST) utiliser le numéro de séquence 0.

Étant donné que la taille des numéros de séquence est de 64 bits, ils ne devraient pas revenir à zéro (wrap around). Si une implémentation TLS devait faire revenir un numéro de séquence à zéro, elle doit (MUST) soit renouveler les clés (section 4.6.3), soit terminer la connexion.

Chaque algorithme AEAD spécifiera une plage de longueurs possibles pour le nonce par enregistrement, de N_MIN octets à N_MAX octets en entrée [RFC5116]. La longueur du nonce par enregistrement de TLS (iv_length) est définie à la plus grande valeur entre 8 octets et N_MIN pour l'algorithme AEAD (voir [RFC5116], section 4). Un algorithme AEAD dont le N_MAX est inférieur à 8 octets ne doit pas (MUST NOT) être utilisé avec TLS. Le nonce par enregistrement pour la construction AEAD est formé comme suit :

  1. Le numéro de séquence d'enregistrement de 64 bits est encodé dans l'ordre des octets du réseau et complété à gauche par des zéros jusqu'à iv_length.

  2. Le numéro de séquence complété est soumis à un XOR avec le client_write_iv ou le server_write_iv statique (selon le rôle).

La quantité résultante (de longueur iv_length) est utilisée comme nonce par enregistrement.

Remarque : il s'agit d'une construction différente de celle de TLS 1.2, qui spécifiait un nonce partiellement explicite.

5.4. Record Padding (Remplissage d'enregistrement)​

Tous les enregistrements TLS chiffrés peuvent être rembourrés afin de gonfler la taille du TLSCiphertext. Cela permet à l'expéditeur de dissimuler la taille du trafic à un observateur.

Lors de la génération d'un enregistrement TLSCiphertext, les implémentations peuvent (MAY) choisir de rembourrer. Un enregistrement non rembourré n'est qu'un enregistrement dont la longueur de rembourrage est nulle. Le rembourrage est une chaîne d'octets de valeur nulle ajoutée au champ ContentType avant le chiffrement. Les implémentations doivent (MUST) mettre tous les octets de rembourrage à zéro avant le chiffrement.

Les enregistrements Application Data peuvent contenir un TLSInnerPlaintext.content de longueur nulle si l'expéditeur le souhaite. Cela permet de générer un trafic de couverture de taille plausible dans des contextes où la présence ou l'absence d'activité peut être sensible. Les implémentations ne doivent pas (MUST NOT) envoyer d'enregistrements Handshake et Alert qui ont un TLSInnerPlaintext.content de longueur nulle ; si un tel message est reçu, l'implémentation réceptrice doit (MUST) terminer la connexion avec une alerte "unexpected_message".

Le rembourrage envoyé est automatiquement vérifié par le mécanisme de protection des enregistrements ; après le déchiffrement réussi d'un TLSCiphertext.encrypted_record, l'implémentation réceptrice parcourt le champ de la fin vers le début jusqu'à ce qu'elle trouve un octet non nul. Cet octet non nul est le type de contenu du message. Ce schéma de rembourrage a été retenu parce qu'il permet de rembourrer tout enregistrement TLS chiffré d'une taille arbitraire (de zéro jusqu'aux limites de taille d'enregistrement TLS) sans introduire de nouveaux types de contenu. La conception impose également que tous les octets de rembourrage soient nuls, ce qui permet une détection rapide des erreurs de rembourrage.

Les implémentations doivent (MUST) limiter leur parcours au texte en clair renvoyé par le déchiffrement AEAD. Si une implémentation réceptrice ne trouve pas d'octet non nul dans le texte en clair, elle doit (MUST) terminer la connexion avec une alerte "unexpected_message".

La présence de rembourrage ne modifie pas les limites globales de taille d'enregistrement : le TLSInnerPlaintext encodé complet ne doit pas (MUST NOT) dépasser 2^14 + 1 octets. Si la longueur maximale de fragment est réduite — comme, par exemple, par l'extension record_size_limit de [RFC8449] — la limite réduite s'applique au texte en clair complet, y compris le type de contenu et le rembourrage.

Le choix d'une politique de rembourrage indiquant quand et combien rembourrer est un sujet complexe qui sort du cadre de cette spécification. Si le protocole de couche application au-dessus de TLS dispose de son propre rembourrage, il peut être préférable de rembourrer les enregistrements TLS Application Data au sein de la couche application. Le rembourrage des enregistrements Handshake ou Alert chiffrés doit toutefois encore être géré au niveau de la couche TLS. Des documents ultérieurs pourront définir des algorithmes de sélection de rembourrage ou définir un mécanisme de demande de politique de rembourrage via des extensions TLS ou d'autres moyens.

5.5. Limits on Key Usage (Limites d'utilisation des clés)​

Il existe des limites cryptographiques sur la quantité de texte en clair qui peut être chiffrée en toute sécurité sous un ensemble de clés donné. [AEAD-LIMITS] fournit une analyse de ces limites en supposant que la primitive sous-jacente (AES ou ChaCha20) n'a pas de faiblesse. Les implémentations devraient (SHOULD) effectuer une mise à jour des clés comme décrit à la section 4.6.3 avant d'atteindre ces limites.

Pour AES-GCM, jusqu'à 2^24.5 enregistrements de taille maximale (environ 24 millions) peuvent être chiffrés sur une connexion donnée tout en conservant une marge de sécurité d'environ 2^-57 pour la sécurité Authenticated Encryption (AE). Pour ChaCha20/Poly1305, le numéro de séquence d'enregistrement reviendrait à zéro avant que la limite de sécurité ne soit atteinte.