Passa al contenuto principale

4. Presentation Language (Linguaggio di presentazione)

Questo documento tratta la formattazione dei dati in una rappresentazione esterna. Verrà utilizzata la seguente sintassi di presentazione molto basilare e definita in modo piuttosto informale. La sintassi trae la sua struttura da diverse fonti. Sebbene assomigli al linguaggio di programmazione "C" nella sua sintassi e a XDR [XDR] sia nella sintassi che nell'intento, sarebbe rischioso tracciare troppi paralleli. Lo scopo di questo linguaggio di presentazione è esclusivamente quello di documentare TLS; non ha applicazioni generali oltre a questo obiettivo particolare.

4.1. Basic Block Size (Dimensione del blocco base)

La rappresentazione di tutti gli elementi di dati è esplicitamente specificata. La dimensione del blocco di dati base è un byte (cioè 8 bit). Gli elementi di dati multi-byte sono concatenazioni di byte, da sinistra a destra, dall'alto verso il basso. Dal flusso di byte, un elemento multi-byte (un numero, nell'esempio) viene formato (usando la notazione C) da:

value = (byte[0] << 8*(n-1)) | (byte[1] << 8*(n-2)) |
... | byte[n-1];

Questo ordine dei byte per i valori multi-byte è il comune ordine dei byte di rete o formato big-endian.

4.2. Miscellaneous (Varie)

I commenti iniziano con /* e terminano con */.

I componenti opzionali sono indicati racchiudendoli tra doppie parentesi quadre [[ ]].

Le entità a singolo byte contenenti dati non interpretati sono di tipo opaque.

4.3. Vectors (Vettori)

Un vettore (array unidimensionale) è un flusso di elementi di dati omogenei. La dimensione del vettore può essere specificata al momento della documentazione o lasciata non specificata fino al runtime. In entrambi i casi, la lunghezza dichiara il numero di byte, non il numero di elementi, nel vettore. La sintassi per specificare un nuovo tipo, T', che è un vettore di lunghezza fissa di tipo T è:

T T'[n];

Qui, T' occupa n byte nel flusso di dati, dove n è un multiplo della dimensione di T. La lunghezza del vettore non è inclusa nel flusso codificato.

Nell'esempio seguente, Datum è definito come tre byte consecutivi che il protocollo non interpreta, mentre Data sono tre Datum consecutivi, che consumano un totale di nove byte.

opaque Datum[3];      /* three uninterpreted bytes */
Datum Data[9]; /* 3 consecutive 3 byte vectors */

I vettori di lunghezza variabile sono definiti specificando un sottoinsieme di lunghezze legali, inclusi i limiti, utilizzando la notazione <floor..ceiling>. Quando questi vengono codificati, la lunghezza effettiva precede il contenuto del vettore nel flusso di byte. La lunghezza sarà nella forma di un numero che consuma tanti byte quanti necessari per contenere la lunghezza massima specificata (ceiling) del vettore. Un vettore di lunghezza variabile con un campo di lunghezza effettiva pari a zero è chiamato vettore vuoto.

T T'<floor..ceiling>;

Nell'esempio seguente, mandatory è un vettore che deve contenere tra 300 e 400 byte di tipo opaque. Non può mai essere vuoto. Il campo di lunghezza effettiva consuma due byte, un uint16, che è sufficiente per rappresentare il valore 400 (vedere Sezione 4.4). D'altra parte, longer può rappresentare fino a 800 byte di dati, o 400 elementi uint16, e può essere vuoto. La sua codifica includerà un campo di lunghezza effettiva di due byte anteposto al vettore. La lunghezza di un vettore codificato deve essere un multiplo pari della lunghezza di un singolo elemento (ad esempio, un vettore di 17 byte di uint16 sarebbe illegale).

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 (Numeri)

Il tipo di dati numerici base è un byte senza segno (uint8). Tutti i tipi di dati numerici più grandi sono formati da serie di byte di lunghezza fissa concatenati come descritto nella Sezione 4.1 e sono anch'essi senza segno. I seguenti tipi numerici sono predefiniti.

uint8 uint16[2];
uint8 uint24[3];
uint8 uint32[4];
uint8 uint64[8];

Tutti i valori, qui e altrove nella specifica, sono memorizzati in ordine di byte di rete (big-endian); l'uint32 rappresentato dai byte esadecimali 01 02 03 04 è equivalente al valore decimale 16909060.

Si noti che in alcuni casi (ad es., parametri DH) è necessario rappresentare gli interi come vettori opachi. In tali casi, sono rappresentati come interi senza segno (cioè, gli ottetti zero iniziali non sono richiesti anche se il bit più significativo è impostato).

4.5. Enumerateds (Tipi enumerati)

È disponibile un ulteriore tipo di dati sparse chiamato enum. Un campo di tipo enum può assumere solo i valori dichiarati nella definizione. Ogni definizione è un tipo diverso. Solo gli enumerati dello stesso tipo possono essere assegnati o confrontati. A ogni elemento di un enumerato deve essere assegnato un valore, come dimostrato nell'esempio seguente. Poiché gli elementi dell'enumerato non sono ordinati, possono essere assegnati loro qualsiasi valore univoco, in qualsiasi ordine.

enum { e1(v1), e2(v2), ... , en(vn) [[, (n)]] } Te;

Un enumerato occupa tanto spazio nel flusso di byte quanto il suo valore ordinale massimo definito. La seguente definizione comporterebbe l'utilizzo di un byte per trasportare campi di tipo Color.

enum { red(3), blue(5), white(7) } Color;

Si può opzionalmente specificare un valore senza il suo tag associato per forzare la definizione della larghezza senza definire un elemento superfluo.

Nell'esempio seguente, Taste consumerà due byte nel flusso di dati ma può assumere solo i valori 1, 2 o 4.

enum { sweet(1), sour(2), bitter(4), (32000) } Taste;

I nomi degli elementi di un'enumerazione hanno ambito limitato al tipo definito. Nel primo esempio, un riferimento completamente qualificato al secondo elemento dell'enumerazione sarebbe Color.blue. Tale qualificazione non è richiesta se il target dell'assegnazione è ben specificato.

Color color = Color.blue;     /* overspecified, legal */
Color color = blue; /* correct, type implicit */

Per gli enumerati che non vengono mai convertiti in rappresentazione esterna, le informazioni numeriche possono essere omesse.

enum { low, medium, high } Amount;

4.6. Constructed Types (Tipi costruiti)

I tipi di struttura possono essere costruiti da tipi primitivi per comodità. Ogni specifica dichiara un nuovo tipo univoco. La sintassi per la definizione è molto simile a quella del C.

struct {
T1 f1;
T2 f2;
...
Tn fn;
} [[T]];

I campi all'interno di una struttura possono essere qualificati utilizzando il nome del tipo, con una sintassi molto simile a quella disponibile per gli enumerati. Ad esempio, T.f2 si riferisce al secondo campo della dichiarazione precedente. Le definizioni di struttura possono essere annidate.

4.6.1. Variants (Varianti)

Le strutture definite possono avere varianti basate su alcune conoscenze disponibili nell'ambiente. Il selettore deve essere un tipo enumerato che definisce le possibili varianti che la struttura definisce. Deve esserci un ramo case per ogni elemento dell'enumerazione dichiarata nel select. I rami case hanno un fall-through limitato: se due rami case si susseguono immediatamente senza campi in mezzo, allora entrambi contengono gli stessi campi. Quindi, nell'esempio seguente, sia "orange" che "banana" contengono V2. Si noti che questo è un nuovo elemento di sintassi in TLS 1.2.

Il corpo della struttura variante può ricevere un'etichetta per riferimento. Il meccanismo mediante il quale la variante viene selezionata al runtime non è prescritto dal linguaggio di presentazione.

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]];

Ad esempio:

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 (Attributi crittografici)

Cinque operazioni crittografiche — firma digitale, cifratura a flusso, cifratura a blocchi, cifratura autenticata con dati aggiuntivi (AEAD) e cifratura a chiave pubblica — sono designate rispettivamente digitally-signed, stream-ciphered, block-ciphered, aead-ciphered e public-key-encrypted. L'elaborazione crittografica di un campo è specificata anteponendo una designazione di parola chiave appropriata alla specifica del tipo del campo. Le chiavi crittografiche sono implicite dallo stato della sessione corrente (vedere Sezione 6.1).

Un elemento firmato digitalmente è codificato come struct DigitallySigned:

struct {
SignatureAndHashAlgorithm algorithm;
opaque signature<0..2^16-1>;
} DigitallySigned;

Il campo algorithm specifica l'algoritmo utilizzato (vedere Sezione 7.4.1.4.1 per la definizione di questo campo). Si noti che l'introduzione del campo algorithm è una modifica rispetto alle versioni precedenti. La signature è una firma digitale che utilizza questi algoritmi sul contenuto dell'elemento. Il contenuto stesso non appare sul filo ma viene semplicemente calcolato. La lunghezza della firma è specificata dall'algoritmo di firma e dalla chiave.

Nella firma RSA, il vettore opaco contiene la firma generata utilizzando lo schema di firma RSASSA-PKCS1-v1_5 definito in [PKCS1]. Come discusso in [PKCS1], il DigestInfo DEVE essere codificato in DER [X680] [X690]. Per gli algoritmi hash senza parametri (che include SHA-1), il campo DigestInfo.AlgorithmIdentifier.parameters DEVE essere NULL, ma le implementazioni DEVONO accettare sia senza parametri che con parametri NULL. Si noti che le versioni precedenti di TLS utilizzavano uno schema di firma RSA diverso che non includeva la codifica DigestInfo.

In DSA, i 20 byte dell'hash SHA-1 vengono passati direttamente attraverso l'algoritmo di firma digitale senza hash aggiuntivo. Questo produce due valori, r e s. La firma DSA è un vettore opaco, come sopra, il cui contenuto è la codifica DER di:

Dss-Sig-Value ::= SEQUENCE {
r INTEGER,
s INTEGER
}

Nota: Nella terminologia attuale, DSA si riferisce all'algoritmo di firma digitale (Digital Signature Algorithm) e DSS si riferisce allo standard NIST. Nelle specifiche SSL e TLS originali, "DSS" veniva utilizzato universalmente. Questo documento utilizza "DSA" per riferirsi all'algoritmo, "DSS" per riferirsi allo standard e utilizza "DSS" nelle definizioni dei punti di codice per continuità storica.

Nella cifratura a flusso, il testo in chiaro viene sottoposto a XOR con una quantità identica di output generato da un generatore di numeri pseudocasuali con chiave crittograficamente sicuro.

Nella cifratura a blocchi, ogni blocco di testo in chiaro viene cifrato in un blocco di testo cifrato. Tutta la cifratura a blocchi viene eseguita in modalità CBC (Cipher Block Chaining) e tutti gli elementi che sono block-ciphered saranno un multiplo esatto della lunghezza del blocco di cifratura.

Nella cifratura AEAD, il testo in chiaro viene simultaneamente cifrato e protetto in integrità. L'input può essere di qualsiasi lunghezza e l'output aead-ciphered è generalmente più grande dell'input per accogliere il valore di controllo dell'integrità.

Nella cifratura a chiave pubblica, viene utilizzato un algoritmo a chiave pubblica per cifrare i dati in modo tale che possano essere decifrati solo con la chiave privata corrispondente. Un elemento cifrato con chiave pubblica viene codificato come un vettore opaco <0..2^16-1>, dove la lunghezza è specificata dall'algoritmo di cifratura e dalla chiave.

La cifratura RSA viene eseguita utilizzando lo schema di cifratura RSAES-PKCS1-v1_5 definito in [PKCS1].

Nell'esempio seguente:

stream-ciphered struct {
uint8 field1;
uint8 field2;
digitally-signed opaque {
uint8 field3<0..255>;
uint8 field4;
};
} UserType;

Il contenuto della struttura interna (field3 e field4) viene utilizzato come input per l'algoritmo di firma/hash, quindi l'intera struttura viene cifrata con una cifratura a flusso. La lunghezza di questa struttura, in byte, sarebbe uguale a due byte per field1 e field2, più due byte per l'algoritmo di firma e hash, più due byte per la lunghezza della firma, più la lunghezza dell'output dell'algoritmo di firma. La lunghezza della firma è nota perché l'algoritmo e la chiave utilizzati per la firma sono noti prima della codifica o decodifica di questa struttura.

4.8. Constants (Costanti)

Le costanti tipizzate possono essere definite per scopi di specifica dichiarando un simbolo del tipo desiderato e assegnandogli valori.

I tipi sottospecificati (opaque, vettori di lunghezza variabile e strutture che contengono opaque) non possono essere assegnati valori. Nessun campo di una struttura o vettore multi-elemento può essere omesso.

Ad esempio:

struct {
uint8 f1;
uint8 f2;
} Example1;

Example1 ex1 = {1, 4}; /* assigns f1 = 1, f2 = 4 */