2
Protocollo
Il funzionamento di SSH è molto simile a quello si SSL (infatti non è una coincidenza che le funzioni crittografiche usate da OpenSSH siano fornite da OpenSSL, una versione open source del Secure Socket Layer sviluppato da Netscape). Entrambi possono instaurare una comunicazione cifrata autenticandosi usando "host key" ed eventualmente certificati che possono essere verificati tramite una autorità fidata.
Ecco come si instaura una connessione:
Primo, il client ed il server si scambiano le loro chiavi pubbliche. Se la macchina del client riceve per la prima volta una data chiave pubblica, SSH chiede all'utente se accettare o meno la chiave. Successivamente client e server negoziano una chiave di sessione che sarà usata per cifrare tutti i dati seguenti attraverso un cifrario a blocchi come il Triplo DES o Blowfish. Il Transport Layer Protocol si occupa di questa prima parte, cioè di fornire autenticazione host, confidenzialità, integrità e opzionalmente compressione. Tale protocollo gira tipicamente su una connessione TCP, ma potrebbe essere usato anche su un qualsiasi altro flusso di dati affidabile.
L’User Authentication Protocol autentica l’utente del client sul server. Sono attualmente supportati tre tipi di autenticazione:
·
Con chiave pubblica: il client invia un pacchetto firmato con
la propria chiave privata. La firma è verificata dal server tramite la chiave
pubblica del client, che il server può possedere da eventuali connessioni
precedenti. Nel caso in cui il server non possieda la chiave pubblica del
client, questo metodo fallisce.
·
Con password: all'utente sul client viene presentato il solito prompt per
l'inserimento della password. Generalmente questo metodo viene utilizzato solo
alla prima connessione ad un server.
·
Host based: è simile al metodo rhosts
di UNIX, ma è più sicuro, in quanto aggiunge ad esso la verifica dell'identità
dell'host tramite la "Host key".
Il server guida l'autenticazione, inviando al client la lista contenente i metodi di autenticazione supportati. Il client sceglie quello che considera più conveniente.
Infine, dopo una autenticazione con successo, tramite il Connection Protocol comincia la vera sessione: il client puo' richiedere una shell remota, l'esecuzione di un comando , un trasferimento di file sicuro, ecc. Il protocollo divide la connessione in canali logici; tutti questi canali sono multiplexati in una singola connessione. In questo modo è possibile accedere a più servizi con un singolo "tunnel cifrato".
L'architettura di SSH e la sua divisione in protocolli è schematizzata nell'immagine seguente:
|
Ogni server dovrebbe avere una Host Key per ogni algoritmo a chiave pubblica supportato. La Host Key del server è utilizzata durante lo scambio di chiavi per verificare che il client stia realmente comunicando con il server corretto. Due differenti modelli di fiducia possono essere utilizzati:
Il protocollo prevede l’opzione che l’associazione nome-chiave non sia controllata alla prima connessione ad un dato host. Questo consente la comunicazione senza che ci sia stata prima comunicazione di chiavi o certificazioni.
2.2 Transport Layer Protocol |
Il Transport Layer Protocol è un protocollo di trasporto sicuro a basso livello. Fornisce crittografia forte, autenticazione degli host crittografica e protezione dell’integrità. Questo protocollo non si occupa dell’autenticazione: questo compito è demandato al protocollo di livello superiore.
Il protocollo è stato progettato per essere semplice, flessibile, per permettere la negoziazione dei parametri e per minimizzare il numero di round-trips. Tutti gli algoritmi (scambio di chiavi, chiave pubblica, cifratura simmetrica ed hash) sono negoziati. Ci si aspetta che in molti ambienti siano necessari solo 2 round-trips per completare lo scambio delle chiavi, l’autenticazione del server, la richiesta dei servizi e la notifica dell’accettazione della richiesta dei servizi. Il caso peggiore è 3 round-trips.
Quando SSH gira su TCP-IP il server si lega alla porta 22. Una volta completata la connessione TCP/IP si procede, in primis, allo scambio delle versioni di protocollo e software, che le due parti si inviano vicendevolmente. Questo scambio serve a garantire la compatibilità. Segue la negoziazione degli algoritmi. Durante questa fase vengono stabiliti gli algoritmi per cifratura, integrità e compressione. Infine abbiamo lo scambio delle chiavi. In questa fase avviene anche la verifica dell'identità del server da parte del client e viene inoltre generato un identificatore di sessione, utilizzato in molte fasi del protocollo e che verrà passato ai protocolli allo strato superiore. Questo protocollo fornisce anche compressione, integrità e cifratura.
Le fasi del Transport Layer Protocol sono riassunte graficamente nell'immagine seguente:
Entrambe le parti devono mandare una stringa identificativa della forma “SSH-protoversion-softwareversion-comments” seguita da un carriage return ed un carattere di newline. La lunghezza massima della stringa è di 255 caratteri, compresi il carriage return ed il newline.
La stringa di versione deve essere in formato US-ASCII, priva di spazi bianchi o segni ‘-‘. La stringa di versione è utilizzata per abilitare estensioni di compatibilità e per indicare le capacità di una implementazione. La stringa di commento dovrebbe contenere informazioni addizionali utili per risolvere problemi dell’utente. Lo scambio di chiavi incomincerà immediatamente dopo che è stato mandato questo identificatore.
Formato generale dei pacchetti
Tutti i pacchetti successivi alla stringa di versione devono avere il seguente formato:
uint32
packet_length byte padding_length byte[n1]payload; n1 =
packet_length - padding_length - 1 byte[n2] random padding; n2
= padding_length byte[m]mac (message authentication
code); m = mac_length |
packet_length
La lunghezza del pacchetto in byte. Non include il MAC o il campo packet_length stesso.
padding_length
La lunghezza del padding in byte.
payload
Il contenuto utile del pacchetto. Se è stata negoziata la compressione, il campo è compresso.
padding
Padding di lunghezza arbitraria, tale che la lunghezza totale di (packet_length || padding_length || payload || padding) è un multiplo della taglia del blocco di cifrario o di 8, che è maggiore. Ci devono essere almeno 4 byte di padding. Il padding deve consistere di byte casuali. La lunghezza massima del padding è di 255 byte.
mac
Message Authentication Code. Se è stata negoziata l’autenticazione del messaggio, questo campo contiene i byte del MAC.
Tutte le implementazioni devono supportare pacchetti di taglia massima di 3500 byte, ma dovrebbero supportare anche pacchetti di taglia superiore.
Negoziazione degli algoritmi
La fase di negoziazione degli algoritmi inizia con entrambe le parti che si inviano la lista degli algoritmi supportati. Ogni lato ha un algoritmo preferito per ogni categoria, e si assume che molte implementazioni ad un dato tempo possano avere lo stesso algoritmo preferito. Ogni lato può indovinare quale algoritmo sta usando l’altro, e può mandare un pacchetto iniziale di scambio chiavi appropriato per quei metodi.
Se la scelta è giusta il pacchetto mandato viene considerato come il primo pacchetto di scambio di chiavi, altrimenti il pacchetto viene ignorato e la parte appropriata deve rinviare correttamente il pacchetto iniziale. Ecco il formato del pacchetto iniziale col quale si negoziano gli algoritmi:
byte
SSH_MSG_KEXINIT byte[16] cookie (random bytes) string kex_algorithms string server_host_key_algorithms string encryption_algorithms_client_to_server string encryption_algorithms_server_to_client string mac_algorithms_client_to_server string mac_algorithms_server_to_client string compression_algorithms_client_to_server string compression_algorithms_server_to_client string languages_client_to_server string languages_server_to_client boolean first_kex_packet_follows uint32 0 (reserved for future extension) |
Ciascuna delle stringhe degli algoritmi deve essere una lista di nomi separati da virgola. Ogni algoritmo supportato deve essere citato in ordine di preferenza. Il primo algoritmo deve essere quello preferito. Ogni stringa deve contenere almeno un nome. Il cookie deve essere un valore casuale generato dal mittente. Il suo scopo è di rendere impossibile per entrambe le parti di determinare pienamente le chiavi e l’identificatore di sessione. Se entrambe le parti fanno la stessa scelta per un determinato algoritmo, quest’ultimo deve essere usato. Altrimenti l’algoritmo deve essere scelto iterando sulla lista del client e scegliere il primo che è supportato anche dal server.
Lo scambio di chiavi parte dalla generazione di due valori: un segreto condiviso K, ed un valore hash di scambio H. La modalità di generazione di tali valori dipende dall'algoritmo utilizzato. Al momento l'unico metodo che tutte le implementazioni devono supportare è Diffie-Hellman, di cui riportiamo un esempio nel paragrafo 2.2.5 Le chiavi per gli algoritmi di cifratura ed autenticazione sono derivate da questi. Il valore di scambio H del primo scambio di chiavi è anche utilizzato come identificatore di sessione, che è unico per la connessione ed una volta calcolato non cambia anche se le chiavi sono riscambiate in seguito. Ogni metodo di scambio di chiavi specifica una funzione hash che è usata nello scambio. Lo stesso algoritmo hash deve essere utilizzato per il calcolo delle chiavi. Qui lo chiameremo HASH. Una volta ottenuti H e K, chiavi e vettori di inizializzazione sono calcolati come HASH con i seguenti parametri:
I dati della chiave vanno scelti dall’inizio dell’output dell’hash. Per gli algoritmi a chiave di lunghezza variabile dovrebbero essere usati 128 bit. Per gli altri algoritmi, si devono prendere tanti byte quanto se ne ha bisogno dall’inizio dell’output dell’hash. Se la chiave deve essere più lunga dell’output dell’hash, si usa il seguente procedimento:
K1 =
HASH(K || H || X || session_id) (X is e.g. "A") K2 = HASH(K || H || K1) K3 = HASH(K || H || K1 || K2) key = K1 || K2 || K3 || ... |
Questo processo è ripetuto finché non si ha abbastanza materiale a disposizione; la chiave è presa dall’inizio di questo valore. Lo scambio delle chiavi termina con entrambe le parti che si scambiano il messaggio SSH_MSG_NEWKEYS. Altri messaggi validi dopo lo scambio delle chiavi sono: SSH_MSG_DEBUG, SSH_MSG_DISCONNECT e SSH_MSG_IGNORE
Esempio: scambio di chiavi
Diffie-Hellman
In questa sezione descriviamo come sono generati da entrambe le parti i valori H e K citati nel paragrafo precedente. Lo scambio di chiavi Diffie-Hellman fornisce un segreto condiviso che non può essere determinato da una sola delle due parti. Lo scambio di chiavi è combinato con una firma con la host key per fornire autenticazione.
Nella seguente descrizione C è il client, S il server; p è un numero primo grande, g è un generatore per un sottogruppo di GF(p), e q è l’ordine del sottogruppo; VS è la stringa di versione di S, VC è la stringa di versione di C; KS è la host key pubblica di S; IC è il messaggio KEXINITdi C (Nota: il messaggio contiene il cookie generato dal client) e IS è il messaggio KEXINIT di S che è stato scambiato prima che questa parte abbia cominciato.
K = ey mod p, H = hash(VC · VS · IC
· IS · KS · e · f · K)
e la firma s su H con la
sua host key privata. S
manda (KS · f · s) a C. L’operazione di firma
può implicare una seconda operazione di hash.
K= fx mod p, H = hash(VC · VS · IC
· IS · KS · e · f · K)
e verifica la firma s su H.
Entrambe le parti non devono mandare o accettare valori di f che non sono nel range [1, p-1]. Se viene violata questa condizione, lo scambio di chiavi fallisce. L’algoritmo di sopra è implementato con i seguenti messaggi. L’algoritmo a chiave pubblica per firmare è negoziato col messaggio KEXINIT. Per prima cosa il client manda il seguente messaggio:
byte
SSH_MSG_KEXDH_INIT mpint
e |
Quindi il server risponde con il seguente messaggio:
byte
SSH_MSG_KEXDH_REPLY
string server public host key and certificates (KS) mpint f string signature of H |
Il valore hash H è computato come l’hash della concatenazione dei seguenti dati:
string
VC, the client's version string (CR and NL excluded) string VS,
the server's version string (CR and NL excluded) string IC,
the payload of the client's SSH_MSG_KEXINIT string IS,
the payload of the server's SSH_MSG_KEXINIT string KS,
the host key mpint e, exchange
value sent by the client mpint f, exchange
value sent by the server mpint K, the
shared secret |
Questo valore è chiamato hash di scambio, ed è utilizzato per autenticare lo scambio delle chiavi. L’hash di scambio dovrebbe essere mantenuto segreto. Il numero primo p è uguale a 21024 - 2960 - 1 + 264 * floor( 2894 p + 129093 ). Il generatore usato con questo numero primo è g = 2
|
Se è stata negoziata la compressione, il campo payload (e solo quello) sarà compresso utilizzando l’algoritmo negoziato. I campi lunghezza e MAC saranno computati dal campo payload compresso. La cifratura sarà eseguita dopo la compressione. Le implementazioni devono consentire di scegliere l’algoritmo indipendentemente per ogni direzione. Sono attualmente definiti i seguenti metodi di compressione.
None
|
REQUIRED
|
No compression |
zlib |
OPTIONAL |
GNU ZLIB (LZ77)
compression |
La fase di compressione è inizializzata dopo lo scambio delle chiavi.
L’integrità dei dati è protetta includendo in ogni pacchetto un MAC che è computato da un segreto condiviso, un numero di sequenza del pacchetto e il contenuto del pacchetto. Gli algoritmi e le chiavi di autenticazione sono negoziate durante lo scambio delle chiavi. Dopo lo scambio delle chiavi, il MAC selezionato sarà computato prima della cifratura dalla concatenazione dei seguenti dati:
mac
= MAC(key, sequence_number || unencrypted_packet)
dove unencrypted_packet è l’intero pacchetto senza MAC (campi lunghezza, payload e padding), e sequence_number è un numero di sequenza del pacchetto rappresentato come un intero a 32 bit privo di segno. Il sequence_number è inizializzato a zero per il primo pacchetto, ed è incrementato dopo ogni pacchetto. Esso non è mai incluso nei dati del pacchetto.
Gli algoritmi di MAC devono girare in maniera indipendente l’uno dall’altro, quindi le implementazioni devono permettere di scegliere gli algoritmi indipendentemente per ogni direzione. Il campo mac deve essere trasmesso alla fine del pacchetto senza essere cifrato. Il numero di byte di questo campo dipendono dall’algoritmo scelto. Sono attualmente definiti i seguenti algoritmi per il MAC:
hmac-sha1 |
REQUIRED |
HMAC-SHA1
(digest length = key length = 20) |
Hmac-sha1-96
|
RECOMMENDED |
first 96 bits
of HMAC-SHA1 (digest length = 12, keylength=20) |
hmac-md5 |
OPTIONAL |
HMAC-MD5
(digest length = key length = 16) |
hmac-md5-96 |
OPTIONAL |
first 96
bits of HMAC-MD5 (digest length = 12, key length =16) |
None |
OPTIONAL |
no MAC;
NOT RECOMMENDED |
L’algoritmo di cifratura sarà negoziato durante lo scambio delle chiavi. I campi lunghezza, padding_length, payload e padding devono essere cifrati con l’algoritmo stabilito. Tutti gli algoritmi di cifratura dovrebbero usare chiavi di lunghezza effettiva pari o superiore a 128 bit.
Gli algoritmi di cifratura devono girare in maniera indipendente l’uno dall’altro, quindi le implementazioni devono permettere di scegliere gli algoritmi indipendentemente per ogni direzione. Sono attualmente definiti i seguenti algoritmi di cifratura:
3des-cbc |
REQUIRED |
three-key
3DES in CBC mode |
blowfish-cbc |
RECOMMENDED |
Blowfish
in CBC mode |
twofish256-cbc |
OPTIONAL |
Twofish
in CBC mode, with 256-bit key |
twofish-cbc |
OPTIONAL |
alias for
"twofish256-cbc" |
twofish192-cbc |
OPTIONAL |
twofish
with 192-bit key |
twofish128-cbc |
RECOMMENDED |
Twofish
with 128-bit key |
aes256-cbc |
OPTIONAL |
AES
(Rijndael) in CBC mode, with 256-bit key |
aes192-cbc |
OPTIONAL |
AES with 192-bit
key |
aes128-cbc |
RECOMMENDED |
AES with
128-bit key |
serpent256-cbc |
OPTIONAL |
Serpent
in CBC mode, with 256-bit key |
serpent192-cbc |
OPTIONAL |
Serpent
with 192-bit key |
serpent128-cbc |
OPTIONAL |
Serpent
with 128-bit key |
Arcfour |
OPTIONAL |
the
ARCFOUR stream cipher |
idea-cbc |
OPTIONAL |
IDEA in
CBC mode |
cast128-cbc |
OPTIONAL |
CAST-128
in CBC mode |
None |
OPTIONAL |
no
encryption; NOT RECOMMENDED |
Algoritmi a chiave pubblica
Questo protocollo è stato progettato per essere in grado di operare con quasi tutti i formati, codifiche e algoritmi di chiave pubblica (per firma o cifratura). Ci sono alcuni aspetti per definire un tipo di chiave pubblica:
Sono attualmente definiti i seguenti formati di chiave pubblica e/o certificati:
ssh-rsa |
RECOMMENDED |
Sign |
Simple
RSA |
ssh-dss |
REQUIRED |
sign |
Simple
DSS |
x509v3-sign-rsa |
RECOMMENDED |
sign |
X.509
certificates (RSA key) |
x509v3-sign-dss |
RECOMMENDED |
sign |
X.509
certificates (DSS key) |
spki-sign-rsa |
OPTIONAL |
sign |
SPKI certificates
(RSA key) |
spki-sign-dss |
OPTIONAL |
sign |
SPKI
certificates (DSS key) |
pgp-sign-rsa |
OPTIONAL |
sign |
OpenPGP
certificates (RSA key) |
pgp-sign-dss |
OPTIONAL |
sign |
OpenPGP
certificates (DSS key) |
2.3 Authentication Protocol |
L’SSH Authentication Protocol è un protocollo di autenticazione dell’utente general-purpose. Gira al di sopra dell’SSH Transport Layer Protocol. Questo protocollo assume che il protocollo sottostante fornisca protezione dell’integrità e confidenzialità.
Quando questo protocollo parte, riceve l’identificatore di sessione dal protocollo al livello inferiore (questo è il valore di scambio H dal primo scambio di chiavi) che identifica univocamente questa sessione.
Autenticazione: schema generale
Il server guida l’autenticazione comunicando al client quali metodi di autenticazione possono essere utilizzati ad ogni istante. Il client ha la libertà di provare i metodi listati dal server in qualsiasi ordine. Questo dà al server il completo controllo sul processo di autenticazione ed al client la libertà di scegliere i metodi più convenienti quando il server offre più metodi.
I metodi sono identificati con dei nomi: Nel metodo "publickey", il client invia al server un pacchetto firmato con la sua chiave privata, il server autorizza l'autenticazione se la firma è valida. Il metodo “password” è quello tradizionale: Il client invia la sua password di login ed il server la verifica. Il metodo "host based" è simile agli stili di autenticazione rhosts e hosts.equiv di UNIX, ma l’identità dell'host del client è controllata più rigorosamente tramite una firma generata con la chiave dell'host del client .Il metodo “none” (nessuna autenticazione) è riservato è non deve essere incluso nella lista dei metodi supportati. Il server deve comunque respingere un tale richiesta, a meno che il client non sia effettivamente autorizzato ad entrare nel sistema senza autenticarsi.
Da notare che, in base alla sua politica, il server può richiedere autenticazioni multiple per un dato utente o gruppo di utenti o per client connessi da un determinato host. Il server dovrebbe avere un timeout per l’autenticazione e disconnettere se l’autenticazione non è stata completata entro quel periodo. Il periodo raccomandato è 10 minuti. Addizionalmente, le implementazioni dovrebbero limitare il numero di tentativi di autenticazione falliti da parte del client. Se non vengono rispettati questi limiti il server dovrebbe disconnettere.
Tutte le richieste di autenticazione devono avere il seguente formato:
byte
SSH_MSG_USERAUTH_REQUEST string user name (in ISO-10646 UTF-8 encoding [RFC-2279]) string service name (in US-ASCII) string method name (US-ASCII) The rest of the packet is method-specific. |
Il campo service name specifica il servizio da avviare al termine dell’autenticazione. Se questo non è disponibile il server può disconnettere immediatamente o anche in seguito. Se il server respinge la richiesta di autenticazione, deve rispondere col seguente pacchetto:
byte
SSH_MSG_USERAUTH_FAILURE string authentications that can continue boolean partial success |
Quando il server accetta l’autenticazione , deve rispondere col seguente messaggio:
byte
SSH_MSG_USERAUTH_SUCCESS |
Nota che questo messaggio è mandato solo quando l’autenticazione è completata, e non anche dopo ogni passo di una sequenza di autenticazione a più metodi. Il client può mandare più richieste di autenticazione senza attendere il responso a quella precedente.
Metodo di autenticazione a chiave pubblica.
L’unico metodo di autenticazione richiesto è quello a chiave pubblica. Tutte le implementazioni lo devono supportare. Con questo metodo, il possesso di una chiave privata serve per autenticarsi. Il metodo funziona inviando una firma creata con la chiave privata dell’utente. Il server deve controllare che la chiave è un autenticatore valido per l’utente e che la firma sia valida. Se entrambe queste cose sono verificate il server deve accettare la richiesta, altrimenti la deve rifiutare. Il client invia il seguente messaggio:
byte
SSH_MSG_USERAUTH_REQUEST string user name string service string "publickey" boolean FALSE string public key algorithm name string public key blob |
Il campo public key blob può contenere certificati. L’algoritmo a chiave pubblica viene proposto dal client. Se il server non lo supporta deve respingere la richiesta.
Il client può includere la firma, generata usando la sua chiave privata, al messaggio senza prima verificare che la sua chiave pubblica sia stata accettata. In tal caso aggiunge il seguente campo al messaggio precendente.
string signature |
La firma è calcolata applicando la cifratura sui seguenti dati:
string
session identifier
byte SSH_MSG_USERAUTH_REQUEST string user name string service string "publickey" boolean TRUE string public key algorithm name string public
key to be used for authentication |
Il server deve rispondere con SSH_MSG_USERAUTH_SUCCESS se non sono necessari altri passi di autenticazione, o con SSH_MSG_USERAUTH_FAILURE se la richiesta ha fallito, o se sono necessari altri passi di autenticazione.
Metodo di autenticazione con password
Tutte le implementazioni dovrebbero supportare questo metodo. Un server può chiedere all’utente di cambiare la password. L’autenticazione con password utilizza il seguente pacchetto:
byte
SSH_MSG_USERAUTH_REQUEST string user name string service string "password" boolean FALSE string plaintext password (ISO-10646 UTF-8) |
È compito del server verificare la password all’interno del proprio database. Anche se la password è trasmessa in chiaro all’interno del pacchetto, l’intero pacchetto è criptato al livello di trasporto. Se non vengono utilizzati confidenzialità e controllo dell’integrità, il cambio della password dovrebbe essere disabilitato. Normalmente il server risponde con un messaggio di successo o di fallimento, ma potrebbe anche chiedere il cambio della password.
Autenticazione Host-based
Questa forma di autenticazione è opzionale. È basata sul nome dell’host su cui gira il client e dell’utente che lo sta utilizzando. Non è consigliabile per siti ad alta sicurezza, ma può risultare molto utile in alcuni ambienti. Quando viene utilizzata si dovrebbe porre molta attenzione a che l’utente non ottenga la chiave privata dell’host.
Questo metodo funziona tramite un algoritmo di firma: il client crea una firma con la chiave privata dell’host che il server verifica tramite la chiave pubblica dell’host stesso. Una volta stabilita l’identità, l’autorizzazione è effettuata basandosi sugli username dell’utente sul client e sul server, e sul nome host del client. L’autenticazione Host-based utilizza il seguente pacchetto:
byte
SSH_MSG_USERAUTH_REQUEST string user name string service string "hostbased" string public key algorithm for host key string public host key and certificates for client
host string client host name (FQDN; US-ASCII) string client user name on the remote host
(ISO-10646 UTF-8) string signature |
Il client include la firma del seguente messaggio generata usando la sua chiave privata. Il server deve verificare che la host key appartenga effettivamente al client, che l’utente specificato abbia il permesso di fare il login, e che la firma sia valida.
2.4 Connection Protocol |
Il Connection Protocol è stato progettato per girare al di sopra dello User Authentication Protocol. Il suo scopo è quello di permettere sessioni interattive di login, esecuzione remota di comandi, inoltro di connessioni TCP/IP ed inoltro di connessioni X11. Il service name per questo protocollo è "ssh-connection".
La connessione è divisa in canali logici; tutti questi canali sono multiplexati in un singolo tunnel cifrato. Tutti gli pseudo-terminali, connessioni inoltrate, ecc. sono considerati canali, essi possono essere aperti sia dal client che dal server, sono identificati da numeri(ad entrambe le parti), che possono essere differenti tra lato client e lato server. Le richieste di apertura di un canale conterranno il numero di canale del mittente ed ogni altro messaggio relativo ad un canale conterrà il numero del canale del ricevente. I canali hanno un meccanismo di controllo del flusso, questo significa che non possono essere inviati dati su di un canale se prima non si è ricevuto messaggio circa la disponibilità della “window space”.
Si possono avere richieste che coinvolgono globalmente lo stato della parte remota e che sono indipendenti dai canali. Un esempio è una richiesta di iniziare un TCP/IP forwarding per una porta specifica. Tutte queste richieste usano il seguente formato:
byte
SSH_MSG_GLOBAL_REQUEST string request name boolean want reply ... request-specific data follows |
Il ricevente risponderà a questo messaggio con SSH_MSG_REQUEST_SUCCESS, SSH_MSG_REQUEST_FAILURE oppure qualche messaggio specifico se il campo “want reply” era stato posto a true.
Quando una delle parti desidera aprire un nuovo canale per prima cosa alloca localmente un numero per quel canale. Fatto ciò invia il seguente messaggio all’altro lato contenente il numero di canale allocato localmente, il tipo di canale desiderato (pty, x11 forwarding ecc.), la dimensione iniziale per la “window size” e la dimensione massima per i pacchetti in transito su quel canale (ad esempio si potrebbero volere pacchetti più piccoli per sessioni interattive come terminali per migliori prestazioni su link lenti).
byte
SSH_MSG_CHANNEL_OPEN string
channel type (restricted to US-ASCII) uint32
sender channel uint32
initial window size uint32
maximum packet size ...
channel type specific data follows |
La parte remota quindi decide se può aprire il canale, in caso affermativo risponde con:
byte
SSH_MSG_CHANNEL_OPEN_CONFIRMATION uint32
recipient channel uint32
sender channel uint32
initial window size uint32
maximum packet size ...
channel type specific data follows |
Dove “recipient channel” è il numero di canale dato nella richiesta originale e “sender channel” è il nuovo numero di canale allocato dalla parte remota. In caso contrario risponderà con:
byte
SSH_MSG_CHANNEL_OPEN_FAILURE uint32
recipient channel uint32
reason code string
additional textual information |
Sono definiti i seguenti “reason code”:
· #define SSH_OPEN_ADMINISTRATIVELY_PROHIBITED 1
· #define SSH_OPEN_CONNECT_FAILED 2
· #define SSH_OPEN_UNKNOWN_CHANNEL_TYPE 3
· #define SSH_OPEN_RESOURCE_SHORTAGE 4
L’ultimo campo può contenere in formazioni utili da mostrare all’utente. Lo scambio dei dati è fatto con messaggi del seguente tipo:
byte
SSH_MSG_CHANNEL_DATA uint32
recipient channel string
data |
Quando
una delle parti non invierà più dati in un canale, deve segnalarlo con
SSH_MSG_CHANNEL_EOF.
byte
SSH_MSG_CHANNEL_EOF
uint32 recipient_channel |
Non viene inviata nessuna risposta esplicita a questo messaggio. Si deve notare che il canale resta aperto dopo questo messaggio ed altri dati possono essere ancora spediti nell’altra direzione. Questo messaggio può essere spedito anche se non c’è “window space”.
Quando una delle parti vuole chiudere una canale spedisce un SSH_MSG_CHANNEL_CLOSE.Un canale è considerato chiuso quando è stato sia inviato che ricevuto un messaggio di chiusura.Nota che non è necessario inviare sempre un messaggio di EOF prima di un messaggio di CLOSE.
Molti
tipi di canali hanno delle estensioni che sono specifiche per quel particolare tipo
di canale; un esempio è la richiesta di un PTY (pseudo terminal) per una
sessione interattiva. Tuttavia tutte le richieste di canali specifici usano il
seguente formato:
byte
SSH_MSG_CHANNEL_REQUEST uint32
recipient channel string
request type (restricted to US-ASCII) boolean
want reply ...
type-specific data |
Se il campo “want reply” è posto a FALSE, non sarà inviata alcun tipo di risposta alla richiesta. In caso contrario il ricevente risponderà al solito o con SSH_MSG_CHANNEL_SUCCESS oppure con SSH_MSG_CHANNEL_FAILURE. Il client ha il permesso di cominciare ad inviare dati per quel canale senza aspettare la risposta alla richiesta.
|
Sessioni interattive |
Una sessione è l’esecuzione remota di un programma. Il programma può essere una shell, una applicazione, una comando di sistema etc… può implicare l’esecuzione di un TTY oppure di un X11 forwarding. Multiple sessioni possono essere attive simultaneamente. Una sessione è iniziata inviando il seguente messaggio:
byte
SSH_MSG_CHANNEL_OPEN string
"session" uint32
sender channel uint32
initial window size uint32
maximum packet size |
Per motivi di sicurezza i client devono rifiutare ogni richiesta di apertura di una sessione in modo da rendere più difficile l’attacco ad un client da parte di un server corrotto. Uno pseudo-terminale viene allocato per la sessione inviando il seguente messaggio:
byte
SSH_MSG_CHANNEL_REQUEST uint32
recipient_channel string
"pty-req" boolean
want_reply string
TERM environment variable value (e.g., vt100) uint32
terminal width, characters (e.g., 80) uint32
terminal height, rows (e.g., 24) uint32 terminal width, pixels (e.g., 480) uint32 terminal height, pixels (e.g., 640) string encoded terminal modes |
Le variabili di ambiente costituiscono un altro importante elemento per lo svolgimento di una sessione interattiva infatti esse possono essere passate in seguito a shell e comandi da far eseguire in un secondo momento. La richiesta di queste variabili avviene attraverso il seguente messaggio:
byte
SSH_MSG_CHANNEL_REQUEST uint32
recipient channel string
"env" boolean
want reply string
variable name string
variable value |
Una volta che la sessione è stata settata, può essere richiesto al server l’esecuzione di un programma. Il programma può essere una shell, un programma applicativo oppure un servizio.Solo una di queste richieste avrà successo per un canale. Il seguente messaggio richiede la shell di default dell’utente(tipicamente definita nel file /etc/passwd):
byte
SSH_MSG_CHANNEL_REQUEST uint32
recipient channel string
"shell" boolean
want reply |
Il
prossimo messaggio richiede al server di iniziare l’esecuzione di un dato comando.
La stringa di comando può contenere anche il path del comando e quindi si rende
necessario prendere precauzioni per prevenire l’esecuzione di comandi non
autorizzati.
byte
SSH_MSG_CHANNEL_REQUEST uint32
recipient channel string
"exec" boolean
want reply |
L’X11
forwarding viene richiesto per una sessione inviando:
byte
SSH_MSG_CHANNEL_REQUEST uint32
recipient channel string
"x11-req" boolean
want reply boolean
single connection string
x11 authentication protocol string
x11 authentication cookie uint32
x11 screen number |
Se
il campo ”single connection” è posto a true non verranno più inoltrate
altre connessini dopo la prima. Il campo “x11 authentication protocol” è il
nome del metodo di autenticazione X11 usato.I canali X11 vengono aperti tramite
una esplicita richiesta. I canali X11 così aperti sono indipendenti dalla
sessione e dopo la chiusura di quest’ultima essi restano aperti.
byte
SSH_MSG_CHANNEL_OPEN string
"x11" uint32
sender channel uint32 initial
window size uint32 maximum
packet size string
originator address (e.g. "192.168.7.38") uint32
originator port |
Al solito il ricevente risponderà con SSH_MSG_CHANNEL_OPEN_CONFIRMATION o SSH_MSG_CHANNEL_OPEN_FAILURE.Le X11_open_request devono essere esplicitamente rifiutate nel momento in cui non è avvenuta prima una richiesta di X11 forwarding.
Questo è un meccanismo che permette di creare un canale di comunicazione sicuro attraverso il quale veicolare qualsiasi tipo connessione TCP. Quello che avviene nella pratica è che viene creato una canale di comunicazione cifrato tra la porta all’indirizzo remoto a cui ci si vuole collegare e una porta locale (libera). Fatto ciò le applicazioni punteranno il collegamento alla porta locale e la connessione verrà inoltrata automaticamente all’ host remoto attraverso un canale sicuro.
Come
è stato accennato, la richiesta di un port forwarding e di tipo globale
ed avviene tramite l’invio del seguente pacchetto:
byte
SSH_MSG_GLOBAL_REQUEST string
"tcpip-forward" boolean
want reply string
address to bind (e.g. "0.0.0.0") uint32
port number to bind |
“Address to bind” e “port number to bind” specificano L’indirizzo IP e la porta a cui è legato il socket da cui ascoltare. L'indirizzo dovrebbe essere “0.0.0.0” se le connessioni sono permesse da ogni parte. Normalmente il forward di porte privilegiate dovrebbe essere consentito solo ad utenti autenticati come privilegiati, inoltre il client deve ignorare questi messaggi dato che nella pratica e solo lui ad inviarli.
Solo
nel momento in cui una connessione arriva alla porta locale per la quale era
stato richiesto un forwarding viene effettivamente aperto il canale che inoltra
la porta verso l’altro lato. A questo scopo viene inviato il seguente pacchetto
al server:
byte
SSH_MSG_CHANNEL_OPEN string
"direct-tcpip" uint32 sender
channel uint32 initial
window size uint32 maximum
packet size string
host to connect uint32 port
to connect string
originator IP address uint32 originator
port |
“Host to connect” e “port to connect” specificano l’host e la porta a cui il server deve attaccare il canale. “Host to connect” può essere sia un nome di dominio che che un indirizzo IP numerico. “Originator IP address” è l’indirizzo IP numerico della macchina da cui proviene la richiesta di connessione e “originator port” è la porta. I canali di questo tipo sono indipendenti da ogni sessione e la chiusura di una sessione non implica in ogni modo che connessioni inoltrate debbano essere chiuse.
Un port Forwarding viene cancellato col seguente messaggio:
byte
SSH_MSG_GLOBAL_REQUEST string
"cancel-tcpip-forward" boolean
want reply string
address_to_bind (e.g. "127.0.0.1") uint32
port number to bind |
2.5 Estensibilità |
Si prevede che il protocollo sarà soggetto ad una evoluzione continua nel tempo, ed alcune organizzazioni potrebbero voler utilizzare i propri algoritmi di cifratura, autenticazione e scambio delle chiavi. Una registrazione centrale di tutte le estensioni è gravosa ma senza essa ci sarebbero conflitti negli identificatori dei metodi, rendendo difficile l’interoperabilità.
Si è perciò scelto di identificare algoritmi, metodi, formati e protocolli di estensione con nomi testuali di un formato specifico: vengono utilizzati i nomi DNS per creare spazi di nomi locali, di modo che estensioni sperimentali o estensioni classificate possano essere utilizzati senza timore di conflitti con altre implementazioni. Lo scopo è quello di mantenere il protocollo quanto più semplice possibile, con un insieme di algoritmi fondamentali quanto più piccolo possibile. Tutte le implementazioni devono supportare tale insieme per assicurare l’interoperabilità.
Il protocollo identifica gli algoritmi e i protocolli con dei nomi. Tutti gli identificatori devono essere stringhe in formato US-ASCII non più lunghe di 64 caratteri. I nomi devono essere case-sensitive. Ci sono degli algoritmi standard che tutte le implementazioni devono supportare. Anche altri algoritmi sono stati definiti nell specifica del protocollo, ma sono opzionali. Inoltre, tutte le organizzazioni potrebbero voler utilizzare i propri algoritmi. Ci sono due formati per i nomi degli algoritmi:
2.6 Politiche Locali |
Il protocollo consente la piena negoziazione di algoritmi e formati di cifratura, integrità, scambio di chiavi e compressione. Le politiche locali dovrebbero guidare la configurazione nelle seguenti scelte:
|
2.7 Aspetti di Sicurezza |
Il Transport Layer Protocol fornisce un canale criptato sicuro su di una rete insicura. Effettua autenticazione del server, scambio di chiavi, cifratura e protezione dell’integrità. Ottiene anche un unico identificatore di sessione che può essere usato dai protocolli allo strato superiore. Questo protocollo è progettato per essere usato su uno strato di trasporto affidabile. Se avvengono errori di trasmissione o manipolazione dei messaggi, la connessione è chiusa. Se ciò accade, la connessione dovrebbe essere ristabilita. Attacchi del tipo “Denial of service” sono quasi impossibili da evitare. Il protocollo non è stato progettato per eliminare canali coperti. Per esempio il padding, messaggi SSH_MSG_IGNORE, ed altri punti del protocollo possono essere usati per passare informazioni coperte, ed il destinatario non ha alcun mezzo affidabile per verificare che informazioni del genere siano state spedite.
Lo scopo dell'Authentication Protocol è di effettuare l’autenticazione dell’utente. Si assume che questo protocollo giri su un protocollo a livello di trasporto sicuro, che ha già autenticato il server, stabilito un canale di comunicazione criptato e computato un identificatore di sessione. È il protocollo di trasporto a fornire segretezza per l’autenticazione con password. Il server può andare in sleep dopo numerosi tentativi di autenticazione falliti per evitare attacchi di ricerca della chiave. Se il livello di trasporto non fornisce cifratura, i metodi di autenticazione che si basano su dati segreti dovrebbero essere disabilitati. Se non fornisce integrità, le richieste di cambiare i dati di autenticazione (password) dovrebbero essere disabilitati per evitare che vengano cambiati da un hacker in un attacco di tipo denial of service, rendendo impossibile un accesso futuro. Il server può decidere, in base alle politiche locali di sicurezza, quali e quanti metodi di autenticazione utilizzare per ciascun utente. Particolare attenzione va posta nel progettare i messaggi di debug. Essi potrebbero rivelare più informazioni sull’host di quante questi non ne voglia rivelare. I messaggi di debug possono comunque essere disabilitati.
Si assume che il Connection Protocol giri al di sopra di un protocollo di trasporto sicuro che ha già autenticato la macchina server, stabilito un canale di comunicazione cifrato, computato un identificatore di sessione, autenticato l’utente. Come si è visto questo protocollo permette l’esecuzione di comandi su macchine remote e permette così anche al server di eseguire comandi sul client. Normalmente le implementazioni devono evitare questa possibilità dato che un server corrotto può essere usato per attaccare tutti i client che si connettono. Migliorata la sicurezza per connessioni al server X11, alla chiusura del canale non restano informazioni riguardo all’autenticazione sul server. Il port forwarding può potenzialmente permettere ad un intruso di scavalcare sistemi di sicurezza come i firewall; tuttavia questo poteva già essere fatto in altri modi. In ogni caso si rende necessaria una buana politica di controllo poiché, in quanto cifrato, il traffico non può essere esaminato dal firewall. Si raccomanda che le implementazioni disabilitino tutte le politiche potenzialmente dannose (ad esempio X11 forwarding e TCP/IP forwarding) nel momento in cui cambiano le chiavi.
In definitiva lo scopo primario del protocollo SSH è migliorare la sicurezza su Internet: