4. Presentation Language (Langage de présentation)
Ce document traite du formatage des données dans une représentation externe. La syntaxe de présentation très basique et quelque peu définie de manière décontractée suivante sera utilisée. La syntaxe tire sa structure de plusieurs sources. Bien qu'elle ressemble au langage de programmation « C » dans sa syntaxe et à XDR [XDR] dans sa syntaxe et son intention, il serait risqué de tirer trop de parallèles. Le but de ce langage de présentation est uniquement de documenter TLS ; il n'a pas d'application générale au-delà de cet objectif particulier.
4.1. Basic Block Size (Taille de bloc de base)
La représentation de tous les éléments de données est explicitement spécifiée. La taille de bloc de données de base est d'un octet (c'est-à-dire 8 bits). Les éléments de données multi-octets sont des concaténations d'octets, de gauche à droite, de haut en bas. À partir du flux d'octets, un élément multi-octets (un nombre, dans l'exemple) est formé (en utilisant la notation C) par :
value = (byte[0] << 8*(n-1)) | (byte[1] << 8*(n-2)) |
... | byte[n-1];
Cet ordre des octets pour les valeurs multi-octets est l'ordre d'octets réseau courant ou le format big-endian.
4.2. Miscellaneous (Divers)
Les commentaires commencent par /* et se terminent par */.
Les composants optionnels sont indiqués en les entourant de doubles crochets [[ ]].
Les entités à un seul octet contenant des données non interprétées sont de type opaque.
4.3. Vectors (Vecteurs)
Un vecteur (tableau unidimensionnel) est un flux d'éléments de données homogènes. La taille du vecteur peut être spécifiée au moment de la documentation ou laissée non spécifiée jusqu'à l'exécution. Dans les deux cas, la longueur déclare le nombre d'octets, et non le nombre d'éléments, dans le vecteur. La syntaxe pour spécifier un nouveau type, T', qui est un vecteur de longueur fixe de type T est :
T T'[n];
Ici, T' occupe n octets dans le flux de données, où n est un multiple de la taille de T. La longueur du vecteur n'est pas incluse dans le flux encodé.
Dans l'exemple suivant, Datum est défini comme étant trois octets consécutifs que le protocole n'interprète pas, tandis que Data est trois Datum consécutifs, consommant un total de neuf octets.
opaque Datum[3]; /* three uninterpreted bytes */
Datum Data[9]; /* 3 consecutive 3 byte vectors */
Les vecteurs de longueur variable sont définis en spécifiant une sous-plage de longueurs légales, inclusive, en utilisant la notation <floor..ceiling>. Lorsqu'ils sont encodés, la longueur réelle précède le contenu du vecteur dans le flux d'octets. La longueur sera sous la forme d'un nombre consommant autant d'octets que nécessaire pour contenir la longueur maximale spécifiée (plafond) du vecteur. Un vecteur de longueur variable avec un champ de longueur réelle de zéro est appelé vecteur vide.
T T'<floor..ceiling>;
Dans l'exemple suivant, mandatory est un vecteur qui doit contenir entre 300 et 400 octets de type opaque. Il ne peut jamais être vide. Le champ de longueur réelle consomme deux octets, un uint16, ce qui est suffisant pour représenter la valeur 400 (voir Section 4.4). D'autre part, longer peut représenter jusqu'à 800 octets de données, ou 400 éléments uint16, et il peut être vide. Son encodage inclura un champ de longueur réelle de deux octets ajouté avant le vecteur. La longueur d'un vecteur encodé doit être un multiple pair de la longueur d'un seul élément (par exemple, un vecteur de 17 octets d'uint16 serait illégal).
opaque mandatory<300..400>;
/* length field is 2 bytes, cannot be empty */
uint16 longer<0..800>;
/* zero to 400 16-bit unsigned integers */
4.4. Numbers (Nombres)
Le type de données numériques de base est un octet non signé (uint8). Tous les types de données numériques plus grands sont formés à partir de séries d'octets de longueur fixe concaténés comme décrit dans la Section 4.1 et sont également non signés. Les types numériques suivants sont prédéfinis.
uint8 uint16[2];
uint8 uint24[3];
uint8 uint32[4];
uint8 uint64[8];
Toutes les valeurs, ici et ailleurs dans la spécification, sont stockées dans l'ordre d'octets réseau (big-endian) ; l'uint32 représenté par les octets hexadécimaux 01 02 03 04 est équivalent à la valeur décimale 16909060.
Notez que dans certains cas (par exemple, les paramètres DH), il est nécessaire de représenter les entiers comme des vecteurs opaques. Dans de tels cas, ils sont représentés comme des entiers non signés (c'est-à-dire que les octets zéro de tête ne sont pas requis même si le bit le plus significatif est défini).
4.5. Enumerateds (Types énumérés)
Un type de données sparse supplémentaire est disponible appelé enum. Un champ de type enum ne peut assumer que les valeurs déclarées dans la définition. Chaque définition est un type différent. Seuls les énumérés du même type peuvent être assignés ou comparés. Chaque élément d'un énuméré doit se voir attribuer une valeur, comme démontré dans l'exemple suivant. Puisque les éléments de l'énuméré ne sont pas ordonnés, ils peuvent se voir attribuer n'importe quelle valeur unique, dans n'importe quel ordre.
enum { e1(v1), e2(v2), ... , en(vn) [[, (n)]] } Te;
Un énuméré occupe autant d'espace dans le flux d'octets que sa valeur ordinale maximale définie. La définition suivante entraînerait l'utilisation d'un octet pour transporter des champs de type Color.
enum { red(3), blue(5), white(7) } Color;
On peut éventuellement spécifier une valeur sans son tag associé pour forcer la définition de la largeur sans définir d'élément superflu.
Dans l'exemple suivant, Taste consommera deux octets dans le flux de données mais ne pourra assumer que les valeurs 1, 2 ou 4.
enum { sweet(1), sour(2), bitter(4), (32000) } Taste;
Les noms des éléments d'une énumération sont limités au type défini. Dans le premier exemple, une référence pleinement qualifiée au deuxième élément de l'énumération serait Color.blue. Une telle qualification n'est pas requise si la cible de l'affectation est bien spécifiée.
Color color = Color.blue; /* overspecified, legal */
Color color = blue; /* correct, type implicit */
Pour les énumérés qui ne sont jamais convertis en représentation externe, l'information numérique peut être omise.
enum { low, medium, high } Amount;
4.6. Constructed Types (Types construits)
Les types de structure peuvent être construits à partir de types primitifs pour plus de commodité. Chaque spécification déclare un nouveau type unique. La syntaxe de définition ressemble beaucoup à celle du C.
struct {
T1 f1;
T2 f2;
...
Tn fn;
} [[T]];
Les champs dans une structure peuvent être qualifiés en utilisant le nom du type, avec une syntaxe très similaire à celle disponible pour les énumérés. Par exemple, T.f2 fait référence au deuxième champ de la déclaration précédente. Les définitions de structure peuvent être imbriquées.
4.6.1. Variants (Variantes)
Les structures définies peuvent avoir des variantes basées sur certaines connaissances disponibles dans l'environnement. Le sélecteur doit être un type énuméré qui définit les variantes possibles que la structure définit. Il doit y avoir un bras case pour chaque élément de l'énumération déclarée dans le select. Les bras case ont un fall-through limité : si deux bras case se suivent immédiatement sans champs entre eux, alors ils contiennent tous deux les mêmes champs. Ainsi, dans l'exemple ci-dessous, « orange » et « banana » contiennent tous deux V2. Notez qu'il s'agit d'un nouvel élément de syntaxe dans TLS 1.2.
Le corps de la structure variante peut recevoir une étiquette pour référence. Le mécanisme par lequel la variante est sélectionnée à l'exécution n'est pas prescrit par le langage de présentation.
struct {
T1 f1;
T2 f2;
....
Tn fn;
select (E) {
case e1: Te1;
case e2: Te2;
case e3: case e4: Te3;
....
case en: Ten;
} [[fv]];
} [[Tv]];
Par exemple :
enum { apple, orange, banana } VariantTag;
struct {
uint16 number;
opaque string<0..10>; /* variable length */
} V1;
struct {
uint32 number;
opaque string[10]; /* fixed length */
} V2;
struct {
select (VariantTag) { /* value of selector is implicit */
case apple:
V1; /* VariantBody, tag = apple */
case orange:
case banana:
V2; /* VariantBody, tag = orange or banana */
} variant_body; /* optional label on variant */
} VariantRecord;
4.7. Cryptographic Attributes (Attributs cryptographiques)
Cinq opérations cryptographiques — signature numérique, chiffrement par chiffrement de flux, chiffrement par chiffrement de bloc, chiffrement authentifié avec données supplémentaires (AEAD) et chiffrement à clé publique — sont désignés respectivement digitally-signed, stream-ciphered, block-ciphered, aead-ciphered et public-key-encrypted. Le traitement cryptographique d'un champ est spécifié en ajoutant une désignation de mot-clé appropriée avant la spécification du type du champ. Les clés cryptographiques sont implicites par l'état de session actuel (voir Section 6.1).
Un élément signé numériquement est encodé comme une struct DigitallySigned :
struct {
SignatureAndHashAlgorithm algorithm;
opaque signature<0..2^16-1>;
} DigitallySigned;
Le champ algorithm spécifie l'algorithme utilisé (voir Section 7.4.1.4.1 pour la définition de ce champ). Notez que l'introduction du champ algorithm est un changement par rapport aux versions précédentes. La signature est une signature numérique utilisant ces algorithmes sur le contenu de l'élément. Le contenu lui-même n'apparaît pas sur le fil mais est simplement calculé. La longueur de la signature est spécifiée par l'algorithme de signature et la clé.
Dans la signature RSA, le vecteur opaque contient la signature générée en utilisant le schéma de signature RSASSA-PKCS1-v1_5 défini dans [PKCS1]. Comme discuté dans [PKCS1], le DigestInfo DOIT être encodé en DER [X680] [X690]. Pour les algorithmes de hachage sans paramètres (qui inclut SHA-1), le champ DigestInfo.AlgorithmIdentifier.parameters DOIT être NULL, mais les implémentations DOIVENT accepter à la fois sans paramètres et avec des paramètres NULL. Notez que les versions antérieures de TLS utilisaient un schéma de signature RSA différent qui n'incluait pas d'encodage DigestInfo.
Dans DSA, les 20 octets du hachage SHA-1 sont exécutés directement à travers l'algorithme de signature numérique sans hachage supplémentaire. Cela produit deux valeurs, r et s. La signature DSA est un vecteur opaque, comme ci-dessus, dont le contenu est l'encodage DER de :
Dss-Sig-Value ::= SEQUENCE {
r INTEGER,
s INTEGER
}
Note : Dans la terminologie actuelle, DSA fait référence à l'algorithme de signature numérique (Digital Signature Algorithm) et DSS fait référence à la norme NIST. Dans les spécifications SSL et TLS originales, « DSS » était utilisé universellement. Ce document utilise « DSA » pour faire référence à l'algorithme, « DSS » pour faire référence à la norme, et utilise « DSS » dans les définitions de points de code pour la continuité historique.
Dans le chiffrement par flux, le texte clair est XOR-é avec une quantité identique de sortie générée à partir d'un générateur de nombres pseudo-aléatoires avec clé cryptographiquement sécurisé.
Dans le chiffrement par bloc, chaque bloc de texte clair est chiffré en un bloc de texte chiffré. Tout le chiffrement par bloc est effectué en mode CBC (Cipher Block Chaining), et tous les éléments qui sont chiffrés par bloc seront un multiple exact de la longueur du bloc de chiffrement.
Dans le chiffrement AEAD, le texte clair est simultanément chiffré et protégé en intégrité. L'entrée peut être de n'importe quelle longueur, et la sortie aead-ciphered est généralement plus grande que l'entrée afin d'accommoder la valeur de vérification d'intégrité.
Dans le chiffrement à clé publique, un algorithme de clé publique est utilisé pour chiffrer les données de telle manière qu'elles ne peuvent être déchiffrées qu'avec la clé privée correspondante. Un élément chiffré par clé publique est encodé comme un vecteur opaque <0..2^16-1>, où la longueur est spécifiée par l'algorithme de chiffrement et la clé.
Le chiffrement RSA est effectué en utilisant le schéma de chiffrement RSAES-PKCS1-v1_5 défini dans [PKCS1].
Dans l'exemple suivant :
stream-ciphered struct {
uint8 field1;
uint8 field2;
digitally-signed opaque {
uint8 field3<0..255>;
uint8 field4;
};
} UserType;
Le contenu de la structure interne (field3 et field4) sont utilisés comme entrée pour l'algorithme de signature/hachage, puis toute la structure est chiffrée avec un chiffrement de flux. La longueur de cette structure, en octets, serait égale à deux octets pour field1 et field2, plus deux octets pour l'algorithme de signature et de hachage, plus deux octets pour la longueur de la signature, plus la longueur de la sortie de l'algorithme de signature. La longueur de la signature est connue car l'algorithme et la clé utilisés pour la signature sont connus avant l'encodage ou le décodage de cette structure.
4.8. Constants (Constantes)
Des constantes typées peuvent être définies à des fins de spécification en déclarant un symbole du type souhaité et en lui assignant des valeurs.
Les types sous-spécifiés (opaque, vecteurs de longueur variable et structures contenant opaque) ne peuvent pas se voir attribuer de valeurs. Aucun champ d'une structure ou d'un vecteur multi-éléments ne peut être élidé.
Par exemple :
struct {
uint8 f1;
uint8 f2;
} Example1;
Example1 ex1 = {1, 4}; /* assigns f1 = 1, f2 = 4 */