Passa al contenuto principale

6. Il protocollo di record TLS (The TLS Record Protocol)

Il protocollo di record TLS (TLS Record Protocol) è un protocollo a livelli. A ogni livello, i messaggi possono includere campi per lunghezza, descrizione e contenuto. Il protocollo di record prende i messaggi da trasmettere, frammenta i dati in blocchi gestibili, comprime facoltativamente i dati, applica un MAC, crittografa e quindi trasmette il risultato. I dati ricevuti vengono decrittografati, verificati, decompressi, riassemblati e quindi consegnati ai client di livello superiore.

Quattro protocolli che utilizzano il protocollo di record sono descritti in questo documento: il protocollo di handshake, il protocollo di avviso, il protocollo di modifica delle specifiche di cifratura e il protocollo dei dati applicativi. Per consentire l'estensione del protocollo TLS, il protocollo di record può supportare tipi di contenuto di record aggiuntivi. I nuovi valori dei tipi di contenuto di record vengono assegnati da IANA nel registro dei tipi di contenuto TLS, come descritto nella sezione 12.

Le implementazioni NON DEVONO (MUST NOT) inviare tipi di record non definiti in questo documento, a meno che non siano negoziati da qualche estensione. Se un'implementazione TLS riceve un tipo di record inaspettato, DEVE (MUST) inviare un avviso unexpected_message.

Qualsiasi protocollo progettato per l'uso su TLS deve essere progettato con attenzione per affrontare tutti i possibili attacchi contro di esso. In pratica, ciò significa che il progettista del protocollo deve essere consapevole di quali proprietà di sicurezza TLS fornisce e non fornisce, e non può fare affidamento in modo sicuro su quest'ultime.

Si noti in particolare che il tipo e la lunghezza di un record non sono protetti dalla crittografia. Se queste informazioni sono di per sé sensibili, i progettisti di applicazioni potrebbero voler adottare misure (riempimento, traffico di copertura) per ridurre al minimo la perdita di informazioni.

6.1. Stati di connessione (Connection States)

Uno stato di connessione TLS (TLS connection state) è l'ambiente operativo del protocollo di record TLS. Specifica un algoritmo di compressione, un algoritmo di crittografia e un algoritmo MAC. Inoltre, i parametri per questi algoritmi sono noti: la chiave MAC e le chiavi di crittografia in blocco per la connessione in entrambe le direzioni di lettura e scrittura. Logicamente, ci sono sempre quattro stati di connessione in sospeso: gli stati di lettura e scrittura correnti e gli stati di lettura e scrittura in attesa. Tutti i record vengono elaborati negli stati di lettura e scrittura correnti. I parametri di sicurezza per gli stati in attesa possono essere impostati dai protocolli di handshake TLS, e il ChangeCipherSpec può rendere selettivamente uno degli stati in attesa corrente, nel qual caso lo stato corrente appropriato viene eliminato e sostituito con lo stato in attesa; lo stato in attesa viene quindi reinizializzato a uno stato vuoto. È illegale rendere corrente uno stato che non è stato inizializzato con parametri di sicurezza. Lo stato corrente iniziale specifica sempre che non verrà utilizzata alcuna crittografia, compressione o MAC.

I parametri di sicurezza per uno stato di lettura e scrittura di connessione TLS vengono impostati fornendo i seguenti valori:

connection end (estremità di connessione)

  • Se questa entità è considerata il "client" o il "server" in questa connessione.

PRF algorithm (algoritmo PRF)

  • Un algoritmo utilizzato per generare chiavi dal secret master (vedere sezioni 5 e 6.3).

bulk encryption algorithm (algoritmo di crittografia in blocco)

  • Un algoritmo da utilizzare per la crittografia in blocco. Questa specifica include la dimensione della chiave di questo algoritmo, se si tratta di un cifrario a blocchi, a flusso o AEAD, la dimensione del blocco del cifrario (se applicabile) e le lunghezze dei vettori di inizializzazione (o nonce) espliciti e impliciti.

MAC algorithm (algoritmo MAC)

  • Un algoritmo da utilizzare per l'autenticazione dei messaggi. Questa specifica include la dimensione del valore restituito dall'algoritmo MAC.

compression algorithm (algoritmo di compressione)

  • Un algoritmo da utilizzare per la compressione dei dati. Questa specifica deve includere tutte le informazioni necessarie all'algoritmo per eseguire la compressione.

master secret (secret master)

  • Un segreto di 48 byte condiviso tra i due peer nella connessione.

client random (casuale client)

  • Un valore di 32 byte fornito dal client.

server random (casuale server)

  • Un valore di 32 byte fornito dal server.

Questi parametri sono definiti nel linguaggio di presentazione come segue:

enum { server, client } ConnectionEnd;

enum { tls_prf_sha256 } PRFAlgorithm;

enum { null, rc4, 3des, aes }
BulkCipherAlgorithm;

enum { stream, block, aead } CipherType;

enum { null, hmac_md5, hmac_sha1, hmac_sha256,
hmac_sha384, hmac_sha512} MACAlgorithm;

enum { null(0), (255) } CompressionMethod;

/* The algorithms specified in CompressionMethod, PRFAlgorithm,
BulkCipherAlgorithm, and MACAlgorithm may be added to. */

struct {
ConnectionEnd entity;
PRFAlgorithm prf_algorithm;
BulkCipherAlgorithm bulk_cipher_algorithm;
CipherType cipher_type;
uint8 enc_key_length;
uint8 block_length;
uint8 fixed_iv_length;
uint8 record_iv_length;
MACAlgorithm mac_algorithm;
uint8 mac_length;
uint8 mac_key_length;
CompressionMethod compression_algorithm;
opaque master_secret[48];
opaque client_random[32];
opaque server_random[32];
} SecurityParameters;

Il livello di record utilizzerà i parametri di sicurezza per generare i seguenti sei elementi (alcuni dei quali non sono richiesti da tutti i cifrari e sono quindi vuoti):

  • client write MAC key (chiave MAC di scrittura client)
  • server write MAC key (chiave MAC di scrittura server)
  • client write encryption key (chiave di crittografia di scrittura client)
  • server write encryption key (chiave di crittografia di scrittura server)
  • client write IV (IV di scrittura client)
  • server write IV (IV di scrittura server)

I parametri di scrittura del client vengono utilizzati dal server durante la ricezione e l'elaborazione dei record e viceversa. L'algoritmo utilizzato per generare questi elementi dai parametri di sicurezza è descritto nella sezione 6.3.

Una volta impostati i parametri di sicurezza e generate le chiavi, gli stati di connessione possono essere istanziati rendendoli stati correnti. Questi stati correnti DEVONO (MUST) essere aggiornati per ogni record elaborato. Ogni stato di connessione include i seguenti elementi:

compression state (stato di compressione)

  • Lo stato corrente dell'algoritmo di compressione.

cipher state (stato di cifratura)

  • Lo stato corrente dell'algoritmo di crittografia. Questo includerà la chiave pianificata per quella connessione. Per i cifrari a flusso, questo conterrà anche tutte le informazioni di stato necessarie per consentire al flusso di continuare a crittografare o decrittografare i dati.

MAC key (chiave MAC)

  • La chiave MAC per questa connessione, generata come sopra.

sequence number (numero di sequenza)

  • Ogni stato di connessione contiene un numero di sequenza, che viene mantenuto separatamente per gli stati di lettura e scrittura. Il numero di sequenza DEVE (MUST) essere impostato a zero ogni volta che uno stato di connessione diventa lo stato attivo. I numeri di sequenza sono di tipo uint64 e non possono superare 2^64-1. I numeri di sequenza non si ripetono. Se un'implementazione TLS dovesse ripetere un numero di sequenza, deve rinegoziare invece. Un numero di sequenza viene incrementato dopo ogni record: in particolare, il primo record trasmesso in un particolare stato di connessione DEVE (MUST) utilizzare il numero di sequenza 0.

6.2. Livello di record (Record Layer)

Il livello di record TLS riceve dati non interpretati dai livelli superiori in blocchi non vuoti di dimensione arbitraria.

6.2.1. Frammentazione (Fragmentation)

Il livello di record frammenta i blocchi di informazioni in record TLSPlaintext che trasportano dati in blocchi di 2^14 byte o meno. I confini dei messaggi del client non vengono preservati nel livello di record (cioè, più messaggi del client dello stesso ContentType POSSONO (MAY) essere raggruppati in un singolo record TLSPlaintext, oppure un singolo messaggio PUÒ (MAY) essere frammentato su più record).

struct {
uint8 major;
uint8 minor;
} ProtocolVersion;

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

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

type

  • Il protocollo di livello superiore utilizzato per elaborare il frammento incluso.

version

  • La versione del protocollo utilizzato. Questo documento descrive TLS versione 1.2, che utilizza la versione 3. Il valore della versione 3.3 è storico, derivante dall'uso di 1 per TLS 1.0. (Vedere appendice A.1.) Si noti che un client che supporta più versioni di TLS potrebbe non sapere quale versione verrà utilizzata prima di ricevere il ServerHello. Vedere l'appendice E per una discussione su quale numero di versione del livello di record dovrebbe utilizzare il ClientHello.

length

  • La lunghezza (in byte) del seguente TLSPlaintext.fragment. La lunghezza NON DEVE (MUST NOT) superare 2^14.

fragment

  • I dati dell'applicazione. Questi dati sono trasparenti e trattati come un blocco indipendente da gestire dal protocollo di livello superiore specificato dal campo type.

Le implementazioni NON DEVONO (MUST NOT) inviare frammenti di lunghezza zero di tipi di contenuto Handshake, Alert o ChangeCipherSpec. I frammenti di lunghezza zero dei dati dell'applicazione POSSONO (MAY) essere inviati poiché potrebbero essere utili come contromisura all'analisi del traffico.

Nota: i dati di diversi tipi di contenuto del livello di record TLS POSSONO (MAY) essere intercalati. I dati dell'applicazione sono generalmente di priorità inferiore per la trasmissione rispetto ad altri tipi di contenuto. Tuttavia, i record NON DEVONO (MUST NOT) attraversare i confini dei messaggi ChangeCipherSpec.

6.2.2. Compressione e decompressione dei record (Record Compression and Decompression)

Tutti i record vengono compressi utilizzando l'algoritmo di compressione definito nello stato della sessione corrente. C'è sempre un algoritmo di compressione attivo; tuttavia, inizialmente è definito come CompressionMethod.null. L'algoritmo di compressione traduce una struttura TLSPlaintext in una struttura TLSCompressed. La compressione deve essere senza perdita e non può aumentare la lunghezza del contenuto di oltre 1024 byte. Se la funzione di decompressione incontra un TLSCompressed.fragment che si decomprime a una lunghezza superiore a 2^14 byte, DEVE (MUST) segnalare un avviso fatale decompression_failure.

struct {
ContentType type;
ProtocolVersion version;
uint16 length;
opaque fragment[TLSCompressed.length];
} TLSCompressed;

length

  • La lunghezza (in byte) del seguente TLSCompressed.fragment. La lunghezza NON DEVE (MUST NOT) superare 2^14 + 1024.

fragment

  • La forma compressa di TLSPlaintext.fragment.

Nota: un'operazione CompressionMethod.null è un'operazione identità; nessun campo viene modificato.

Nota sull'implementazione: le funzioni di decompressione sono responsabili di garantire che i messaggi non possano causare overflow di buffer interni.

6.2.3. Protezione del payload del record (Record Payload Protection)

Le funzioni di crittografia e MAC traducono una struttura TLSCompressed in un TLSCiphertext. Le funzioni di decrittografia invertono il processo. Il MAC del record include anche un numero di sequenza in modo che i messaggi mancanti, aggiuntivi o ripetuti siano rilevabili.

struct {
ContentType type;
ProtocolVersion version;
uint16 length;
select (SecurityParameters.cipher_type) {
case stream: GenericStreamCipher;
case block: GenericBlockCipher;
case aead: GenericAEADCipher;
} fragment;
} TLSCiphertext;

type

  • Il campo type è identico a TLSCompressed.type.

version

  • Il campo version è identico a TLSCompressed.version.

length

  • La lunghezza (in byte) del seguente TLSCiphertext.fragment. La lunghezza NON DEVE (MUST NOT) superare 2^14 + 2048.

fragment

  • La forma crittografata di TLSCompressed.fragment, con il MAC.

6.2.3.1. Cifrario a flusso nullo o standard (Null or Standard Stream Cipher)

I cifrari a flusso (incluso BulkCipherAlgorithm.null; vedere appendice A.6) convertono le strutture TLSCompressed.fragment da e verso le strutture TLSCiphertext.fragment a flusso.

stream-ciphered struct {
opaque content[TLSCompressed.length];
opaque MAC[SecurityParameters.mac_length];
} GenericStreamCipher;

Il MAC viene generato come segue:

MAC(MAC_write_key, seq_num +
TLSCompressed.type +
TLSCompressed.version +
TLSCompressed.length +
TLSCompressed.fragment);

dove "+" indica la concatenazione.

seq_num

  • Il numero di sequenza per questo record.

MAC

  • L'algoritmo MAC specificato in SecurityParameters.mac_algorithm.

Si noti che il MAC viene calcolato prima della crittografia. Il cifrario a flusso crittografa l'intero blocco, incluso il MAC.

6.2.3.2. Cifrario a blocchi CBC (CBC Block Cipher)

Per i cifrari a blocchi (come 3DES o AES), le funzioni di crittografia e MAC convertono le strutture TLSCompressed.fragment da e verso le strutture TLSCiphertext.fragment a blocchi.

struct {
opaque IV[SecurityParameters.record_iv_length];
block-ciphered struct {
opaque content[TLSCompressed.length];
opaque MAC[SecurityParameters.mac_length];
uint8 padding[GenericBlockCipher.padding_length];
uint8 padding_length;
};
} GenericBlockCipher;

Il MAC viene generato come descritto nella sezione 6.2.3.1.

IV

  • Il vettore di inizializzazione (IV) DOVREBBE (SHOULD) essere scelto casualmente e DEVE (MUST) essere imprevedibile. Si noti che nelle versioni di TLS precedenti alla 1.1, non c'era un campo IV e l'ultimo blocco di testo cifrato del record precedente (il "residuo CBC") veniva utilizzato come IV. Questo è stato modificato per prevenire gli attacchi descritti in [CBCATT]. Per i cifrari a blocchi, la lunghezza dell'IV è SecurityParameters.record_iv_length, che è uguale a SecurityParameters.block_length.

padding

  • Riempimento aggiunto per forzare la lunghezza del testo in chiaro a essere un multiplo intero della lunghezza del blocco del cifrario a blocchi. Il riempimento PUÒ (MAY) essere di qualsiasi lunghezza fino a 255 byte, purché risulti in TLSCiphertext.length essere un multiplo intero della lunghezza del blocco. Lunghezze maggiori del necessario potrebbero essere desiderabili per contrastare gli attacchi contro un protocollo basati sull'analisi delle lunghezze dei messaggi scambiati. Ogni uint8 nel vettore di dati di riempimento DEVE (MUST) essere riempito con il valore della lunghezza di riempimento. Il destinatario DEVE (MUST) verificare questo riempimento e DEVE (MUST) restituire un avviso bad_record_mac se il riempimento non è corretto. Il destinatario DOVREBBE (SHOULD) esaminare il riempimento il meno possibile per fornire un tempo di risposta il più uniforme possibile, indipendentemente dal fatto che il riempimento sia corretto o meno.

padding_length

  • La lunghezza del riempimento DEVE (MUST) essere tale che la dimensione totale della struttura GenericBlockCipher sia un multiplo della lunghezza del blocco del cifrario. I valori legali vanno da zero a 255, inclusi. Questa lunghezza specifica la lunghezza del campo di riempimento escludendo il campo padding_length stesso.

La lunghezza dei dati crittografati (TLSCiphertext.length) è maggiore di uno rispetto alla somma di SecurityParameters.block_length, TLSCompressed.length, SecurityParameters.mac_length e padding_length.

Esempio: se la lunghezza del blocco è di 8 byte, la lunghezza del contenuto (TLSCompressed.length) è di 61 byte e la lunghezza del MAC è di 20 byte, allora la lunghezza prima del riempimento è di 82 byte (questo non include l'IV). Pertanto, la lunghezza del riempimento modulo 8 deve essere uguale a 6 per rendere la lunghezza totale un multiplo pari di 8 byte (la lunghezza del blocco). La lunghezza del riempimento può essere 6, 14, 22 e così via, fino a 254. Se la lunghezza del riempimento fosse il minimo necessario, 6, il riempimento sarebbe di 6 byte, ciascuno contenente il valore 6. Quindi, gli ultimi 8 ottetti del GenericBlockCipher prima della crittografia a blocchi sarebbero xx 06 06 06 06 06 06 06, dove xx è l'ultimo ottetto del MAC.

Nota: con i cifrari a blocchi in modalità CBC (Cipher Block Chaining), è fondamentale che l'intero testo in chiaro del record sia noto prima che venga trasmesso qualsiasi testo cifrato. Altrimenti, è possibile per l'attaccante montare l'attacco descritto in [CBCATT].

Nota sull'implementazione: Canvel et al. [CBCTIME] hanno dimostrato un attacco temporale sul riempimento CBC basato sul tempo richiesto per calcolare il MAC. Per difendersi da questo attacco, le implementazioni DEVONO (MUST) garantire che il tempo di elaborazione del record sia sostanzialmente lo stesso, indipendentemente dal fatto che il riempimento sia corretto o meno. In generale, il modo migliore per farlo è calcolare il MAC anche se il riempimento non è corretto, e solo allora rifiutare il pacchetto. Ad esempio, se il riempimento sembra essere errato, l'implementazione potrebbe assumere un riempimento di lunghezza zero e quindi calcolare il MAC. Questo lascia un piccolo canale temporale, poiché le prestazioni del MAC dipendono in una certa misura dalla dimensione del frammento di dati, ma non si ritiene che sia abbastanza grande da essere sfruttabile, a causa della grande dimensione del blocco dei MAC esistenti e della piccola dimensione del segnale temporale.

6.2.3.3. Cifrari AEAD (AEAD Ciphers)

Per i cifrari AEAD [AEAD] (come CCM o GCM), la funzione AEAD converte le strutture TLSCompressed.fragment da e verso le strutture TLSCiphertext.fragment AEAD.

struct {
opaque nonce_explicit[SecurityParameters.record_iv_length];
aead-ciphered struct {
opaque content[TLSCompressed.length];
};
} GenericAEADCipher;

I cifrari AEAD prendono come input una singola chiave, un nonce, un testo in chiaro e "dati aggiuntivi" da includere nel controllo di autenticazione, come descritto nella sezione 2.1 di [AEAD]. La chiave è client_write_key o server_write_key. Non viene utilizzata alcuna chiave MAC.

Ogni suite di cifratura AEAD DEVE (MUST) specificare come viene costruito il nonce fornito all'operazione AEAD e qual è la lunghezza della parte GenericAEADCipher.nonce_explicit. In molti casi, è appropriato utilizzare la tecnica del nonce parzialmente implicito descritta nella sezione 3.2.1 di [AEAD]; con record_iv_length che è la lunghezza della parte esplicita. In questo caso, la parte implicita DOVREBBE (SHOULD) essere derivata da key_block come client_write_iv e server_write_iv (come descritto nella sezione 6.3), e la parte esplicita è inclusa in GenericAEADCipher.nonce_explicit.

Il testo in chiaro è il TLSCompressed.fragment.

I dati autenticati aggiuntivi, che denotiamo come additional_data, sono definiti come segue:

additional_data = seq_num + TLSCompressed.type +
TLSCompressed.version + TLSCompressed.length;

dove "+" indica la concatenazione.

L'aead_output consiste nel testo cifrato prodotto dall'operazione di crittografia AEAD. La lunghezza sarà generalmente maggiore di TLSCompressed.length, ma di un importo che varia con il cifrario AEAD. Poiché i cifrari potrebbero incorporare il riempimento, l'importo dell'overhead potrebbe variare con diversi valori TLSCompressed.length. Ogni cifrario AEAD NON DEVE (MUST NOT) produrre un'espansione maggiore di 1024 byte. Simbolicamente,

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

Per decrittografare e verificare, il cifrario prende come input la chiave, il nonce, gli "additional_data" e il valore AEADEncrypted. L'output è il testo in chiaro o un errore che indica che la decrittografia è fallita. Non c'è un controllo di integrità separato. Cioè:

TLSCompressed.fragment = AEAD-Decrypt(write_key, nonce,
AEADEncrypted,
additional_data)

Se la decrittografia fallisce, DEVE (MUST) essere generato un avviso fatale bad_record_mac.

6.3. Calcolo delle chiavi (Key Calculation)

Il protocollo di record richiede un algoritmo per generare le chiavi richieste dallo stato di connessione corrente (vedere appendice A.6) dai parametri di sicurezza forniti dal protocollo di handshake.

Il secret master viene espanso in una sequenza di byte sicuri, che viene quindi divisa in una chiave MAC di scrittura client, una chiave MAC di scrittura server, una chiave di crittografia di scrittura client e una chiave di crittografia di scrittura server. Ognuno di questi viene generato dalla sequenza di byte in quell'ordine. I valori inutilizzati sono vuoti. Alcuni cifrari AEAD potrebbero richiedere ulteriormente un IV di scrittura client e un IV di scrittura server (vedere sezione 6.2.3.3).

Quando vengono generate chiavi e chiavi MAC, il secret master viene utilizzato come fonte di entropia.

Per generare il materiale chiave, calcola

key_block = PRF(SecurityParameters.master_secret,
"key expansion",
SecurityParameters.server_random +
SecurityParameters.client_random);

fino a quando è stato generato un output sufficiente. Quindi, il key_block viene partizionato come segue:

client_write_MAC_key[SecurityParameters.mac_key_length]
server_write_MAC_key[SecurityParameters.mac_key_length]
client_write_key[SecurityParameters.enc_key_length]
server_write_key[SecurityParameters.enc_key_length]
client_write_IV[SecurityParameters.fixed_iv_length]
server_write_IV[SecurityParameters.fixed_iv_length]

Attualmente, il requisito di materiale chiave più grande è per AES_256_CBC_SHA256. Richiede 2 x 32 byte di chiavi e 2 x 32 byte di chiavi MAC, per un totale di 128 byte di materiale chiave. Al contrario, i dati casuali forniti dal client e dal server (64 byte in totale) sono sufficienti. Dati aggiuntivi possono anche essere forniti dalla PRF, ma nel caso di TLS 1.2, è sempre impostato sulla stringa vuota.

Gli algoritmi di crittografia esportabili (che non sono più supportati) richiedevano un'elaborazione aggiuntiva per derivare chiavi più corte dal key_block. I cifrari di esportazione NON DOVREBBERO (SHOULD NOT) essere utilizzati nelle nuove implementazioni.