Finalmente, acquisiti i concetti esposti nei precedenti paragrafi, è possibile parlare del funzionamento di Kerberos. Lo faremo elencando e descrivendo ciascuno dei pacchetti che durante l’autenticazione intercorrono fra client e KDC e fra client e server applicativi. Si noti fin d’ora, che un server applicativo non comunica mai direttamente con il Key Distribution Center: i ticket di servizio, benchè impacchettati dal TGS pervengono al servizio a cui sono destinati solo attraverso il client che vuole accedervi. Di seguito sono elencati i messaggi di cui ci occuperemo (vedi anche la figura sotto):
- AS_REQ è la richiesta iniziale di autenticazione dell’utente (fatta con il kinit tanto per intenderci). Tale messaggio è diretto alla componente del KDC nota come Authentication Server (AS);
- AS_REP è la risposta dell’Authentication Server alla richiesta precedente. Sostanzialmente contiene il TGT (criptato con la chiave segreta del TGS) e la chiave di sessione (criptata con la chiave segreta dell’utente richiedente);
- TGS_REQ è la richiesta da parte del client rivolta al Ticket Granting Server (TGS) per un ticket di servizio. Dentro questo pacchetto viaggia il TGT ottenuto dal messaggio precedente e un autenticatore generato dal client e criptato con la session key;
- TGS_REP è la risposta del Ticket Granting Server alla richiesta precedente. Ci si trova dentro il service ticket richiesto (criptato con la chiave segreta del servizio) e una chiave di sessione di servizio generata dal TGS e criptata con la precedente chiave di sessione generata dall’AS;
- AP_REQ è la richiesta che il client manda ad un server applicativo per accedere ad un servizio. Le componenti sono il ticket di servizio ottenuto dal TGS con la risposta precedente e un autenticatore generato sempre dal client, ma questa volta criptato con la chiave di sessione del servizio (generata dal TGS);
- AP_REP è la risposta che il server applicativo dà al client per provare di essere veramente il server che il client si aspetta. Questo pacchetto non è sempre richiesto. Il client lo richiede al server solo quando è necessaria la mutua autenticazione.
Ora ognuna delle precedenti fasi viene descritta più approfonditamente facendo riferimento a Kerberos 5 ma evidenziando comunque le differenze con la versione 4. Tuttavia, si tenga presente, che il protocollo Kerberos è piuttosto complicato e non è nell’intenzione di questo documento essere di guida a chi voglia conoscerne i dettagli precisi di funzionamento (d’altra parte questi sono già scritti sul RFC1510). Quello che segue è volutamente approssimato, ma sufficiente, a chi esamina i log del KDC, per capire le varie transizioni di autenticazione ed eventuali problemi che si presentano.
Nota: nei successivi paragrafi racchiuderemo tra parentesi tonde () i dati che viaggiano in chiaro, mentre tra parentesi graffe {} quelli criptati: ( x , y , z ) significa che x, y, z sono in chiaro; { x , y , z }K indica che x, y, z sono criptati tutti insieme con la chiave simmetrica K. Si precisa inoltre, che l’ordine con cui le componenti un pacchetto sono elencate non ha niente a che fare con l’ordine vero che si trova nei messaggi reali (UDP o TCP). Qui la trattazione è molto astratta. Se si vogliono i dettagli si faccia riferimento all’RFC1510 muniti di buona conoscenza del protocollo descrittivo ASN.1
1.4.1 Authentication Server Request (AS_REQ)
In questa fase, nota come richiesta di autenticazione iniziale, il client (kinit) chiede al KDC (più precisamente all’AS) un Ticket Granting Ticket. La richiesta è completamente in chiaro ed appare cosi:AS_REQ = ( PrincipalClient , PrincipalService , IP_list , Lifetime )
dove: PrincipalClient è il principal associato all’utente che chiede di autenticarsi (per es. pippo@EXAMPLE.COM); PrincipalService è il principal associato al servizio per cui si chiede il ticket e quindi è la stringa “krbtgt/REALM@REALM” (vedi la nota *); IP_list è una lista di indirizzi IP che indica gli host da cui è possibile usare il ticket che verrà emesso (vedi nota **); Lifetime è il tempo massimo di validità (richiesto) per il ticket da emettere.
Nota *: può sembrare superfluo aggiungere in una richiesta di autenticazione iniziale il Principalservice visto che questo andrebbe costantemente impostato al principal del TGS e cioè krbtgt/REALM@REALM. Tuttavia non è così, infatti, un utente, prevedendo che durante la sua sessione di lavoro userà un solo servizio, non avvantaggiandosi quindi del Single Sign-on, può chiedere all’AS direttamente il ticket per quel servizio, saltando in questa maniera la successiva richiesta al TGS. Dal punto di vista operativo (MIT 1.3.6) basta il comando: kinit -S imap/mbox.example.com@EXAMPLE.COM pippo@EXAMPLE.COM
Nota **: IP_list potrebbe essere anche nullo. In tal caso il corrispondente ticket sarà utilizzabile da qualsiasi macchina. Si risolve così il problema dei client che sono sotto NAT e la cui richiesta arriverebbe al servizio con un indirizzo sorgente non uguale a quello del richiedente ma a quello del router che fa il NAT. Nel caso invece di macchine con più schede di rete, IP_list dovrebbe contenere gli IP di tutte le schede: sarebbe infatti difficile prevedere a priori con quale connessione il server che fornisce il servizio verrebbe contattato.
1.4.2 Authentication Server Reply (AS_REP)
All’arrivo della richiesta precedente, l’AS controlla se PrincipalClient e PrincipalService esistono nel database del KDC: se almeno uno dei due non esiste viene inviato al client un messaggio d’errore, altrimenti l’Authentication Server elabora la risposta come segue:
- Crea in maniera random una chiave di sessione che sarà il segreto condiviso tra il client e il TGS. Diciamola SKTGS;
- Crea il Ticket Granting Ticket mettendogli dentro il principal del richiedente, il principal del servizio (in genere è krbtgt/REALM@REALM, ma leggi la nota * del paragrafo precedente), la lista di indirizzi IP (queste prime tre informazioni sono copiate così come arrivano dal pacchetto AS_REQ), la data e l’ora (del KDC) in formato di timestamp, il lifetime (vedi nota *) e infine la session key SKTGS; il Ticket Granting Ticked appare cioè cosi: TGT = ( PrincipalClient , krbtgt/REALM@REALM , IP_list , Timestamp , Lifetime , SKTGS )
- Genera e spedisce la risposta contenente: il ticket creato prima, cifrato con la chiave segreta del servizio (diciamola KTGS); il principale del servizio, il timestamp, il lifetime e la session key tutto criptato con la chiave segreta dell’utente richiedente il servizio (diciamola KUser). In sintesi: AS_REP = { PrincipalService , Timestamp , Lifetime , SKTGS }KUser { TGT }KTGS
Potrebbe sembrare che tale messaggio contenga informazioni ridondanti (PrincipalService, il timestamp, lifetime, la session key). Ma non è cosi: le informazioni presenti nel TGT, essendo criptate con la chiave segreta del server non potranno essere lette dal client e pertanto vanno ripetute. A questo punto, quando il client riceve il messaggio di risposta, chiederà all’utente di immettere la password. A quest’ultima viene concatenato il salt e poi applicata la funzione string2key con il cui risultato si tenta di decriptare la parte di messaggio cifrata dal KDC con la la chiave segreta dell’utente. Se l’utente è veramente chi dice di essere e quindi ha immesso la password giusta, l’operazione di decifratura andrà a buon fine e si potrà estrarre la session key che insieme al TGT (che invece rimane criptato) viene conservata nella cache delle credenziali dell’utente.
Nota *: il lifetime effettivo, cioè quello che viene messo nel ticket, è il minimo tra i seguenti valori: il lifetime richiesto dal client, quello contenuto nel principal dell’utente e quello nel principal del servizio. In realtà poi, a livello implementativo ci può essere un altro limite imposto dalla configurazione del KDC e che viene applicato a qualsiasi ticket.
1.4.3 Ticket Granting Server Request (TGS_REQ)
A questo punto, l’utente, che ha già dimostrato di essere chi dice di essere (quindi ha nella sua cache delle credenziali un TGT e una chiave di sessione SKTGS) e vuole accedere ad un servizio per il quale non ha ancora un ticket adatto, spedisce al Ticket Granting Server una richiesta (TGS_REQ) costruendola nel seguente modo:
- Crea un autenticatore con il principal dell’utente, il timestamp della macchina client e cripta tutto con la chiave di sessione condivisa con il TGS, cioè: Authenticator = { PrincipalClient , Timestamp }SKTGS
- Crea il pacchetto di richiesta contenente: in chiaro il principal del servizio per cui si vuole il ticket e un lifetime; il Ticket Granting Ticket che è già criptato con la chiave del TGS; l’autenticatore appena creato. In sintesi: TGS_REQ = ( PrincipalService , Lifetime , Authenticator) { TGT }KTGS
1.4.4 Ticket Granting Server Reply (TGS_REP)
Quando arriva la richiesta precedente, il TGS verifica dapprima che il principal del servizio richiesto (PrincipalService) esista nel database del KDC. Se esiste, apre il TGT utilizzando la chiave del krbtgt/REAM@REALM ed estrae la session key (SKTGS) con la quale decripta l’autenticatore. Affinché l’emissione del ticket di servizio possa avvenire controlla che abbiano esito positivo le condizioni:
- Il TGT non sia scaduto;
- Il PrincipalClient presente nell’autenticatore coincida con quello presente nel TGT;
- L’autenticatore non sia presente nella replay cache e non sia scaduto;
- Se IP_list non è nulla verifica che l’indirizzo IP sorgente del pacchetto di richiesta (TGS_REQ) sia uno di quelli contenuti nella lista;
Le precedenti condizioni verificate dimostrano che il TGT appartiene veramente all’utente che ha fatto la richiesta e pertanto il TGS inizia a processare la risposta come segue:
- Crea in maniera random una chiave di sessione che sarà il segreto condiviso tra il client e il servizio. Diciamola SKService;
- Crea il ticket di servizio mettendogli dentro il principal del richiedente, il principal del servizio, la lista di indirizzi IP, la data e l’ora (del KDC) in formato di timestamp, il lifetime (come minimo tra il lifetime del TGT e quello associato al principal del servizio) e infine la session key SKService. Detto TService il nuovo ticket si ha: TService = ( PrincipalClient , PrincipalService , IP_list , Timestamp , Lifetime , SKService )
- Spedisce il messaggio di risposta contenente: il ticket creato prima cifrato con la chiave segreta del servizio (diciamola KService); il principale del servizio, il timestamp, il lifetime e la nuova session key tutto criptato con la chiave di sessione estratta dal TGT. In sintesi: TGS_REP = { PrincipalService , Timestamp , Lifetime , SKService }SKTGS { TService }KService Quando il client riceve la risposta, avendo nella cache delle credenziali la session key SKTGS, potrà decifrare la parte di messaggio contente l’altra chiave di sessione e memorizzarla insieme al ticket di servizio TService che però rimane criptato.
1.4.5 Application Request (AP_REQ)
Il client, avendo ormai le credenziali per accedere al servizio (cioè il ticket e la relativa session key), può chiedere al server applicativo l’accesso alla risorsa mediante un messaggio AP_REQ. Si tenga presente, che a differenza dei precedenti messaggi in cui era coinvolto il KDC, l’AP_REQ non è standard, ma varia da applicazione ad applicazione. Si lascia cioè al programmatore applicativo il compito di stabilire la strategia con cui il client sfrutta le sue credenziali per provare al server la sua identità. Tuttavia, come esempio possiamo considerare la seguente strategia:
- Il client crea un autenticatore contenente il principal dell’utente, il timestamp e cripta tutto con la chiave di sessione SKService che condivide con il server applicativo, cioè: Authenticator = { PrincipalClient , Timestamp }SKService
- Crea il pacchetto di richiesta contenente il ticket di servizio TService che è criptato con sua chiave segreta e l’autenticatore appena creato. In sintesi: AP_REQ = Authenticator { TService }KService
Quando arriva la richiesta precedente, il server applicativo apre il ticket utilizzando la chiave segreta del servizio richiesto ed estrae la session key SKService con la quale decripta l’autenticatore. Per stabilire che l’utente richiedente è autentico e quindi concedere accesso al servizio, il server verifica le seguenti condizioni:
- Il ticket non sia scaduto;
- Il PrincipalClient presente nell’autenticatore coincida con quello presente nel ticket;
- L’autenticatore non sia presente nella replay cache e non sia scaduto;
- Se IP_list (estratta dal ticket) non è nulla verifica che l’indirizzo IP sorgente del pacchetto di richiesta (AP_REQ) sia uno di quelli contenuti nella lista;
Nota: la precedente strategia assomiglia molto a quella utilizzata dal Ticket Granting Server per verificare l’autenticità dell’utente richiedente un ticket di servizio. Ma ciò non dovrebbe meravigliare, poiché già si è detto che il TGS può esso stesso essere pensato come un server applicativo il cui servizio è di fornire ticket a chi prova la sua identità con un TGT.
1.4.7 Pre-Authentication
Come si è visto nella descrizione dell’Authetication Server Reply (AS_REP), il KDC, prima di distribuire un ticket, controlla semplicemente che il principal del richiedente e del fornitore il servizio esistano nel database. In particolare poi, se si tratta della richiesta di un TGT la cosa è ancora più facile perché krbtgt/REALM@REALM esiste sicuramente e quindi basta conoscere l’esistenza del principal di un utente per poter ottenere con una semplice richiesta di autenticazione iniziale un TGT. Ovviamente, tale TGT, se la richiesta è avvenuta da parte di un utente illegittimo, non potrà essere usato poiché questi non conoscendo la password non potrà ottenere la session key con cui forgiare un autenticatore valido. Tuttavia, questo ticket, ottenuto in maniera così tanto gratuita può essere sottoposto ad un attacco brute-force nel tentativo di indovinare la chiave a lungo termine del servizio a cui il ticket è destinato. È chiaro, che indovinare il segreto di un servizio non è una cosa banale neanche con le attuali potenze di calcolo, ma comunque, con Kerberos 5, allo scopo di rafforzare la sicurezza, si introduce il concetto di pre-autenticazione. Se cioè, le policy (configurabili) del KDC richiedono la preautenticazione, alla richiesta iniziale da parte di un client, l’Authentication Server risponde con un pacchetto di errore indicante la necessità di preautenticarsi. Il client, visto l’errore, chiede all’utente l’immissione della password e riformula la richiesta ma questa volta aggiungendovi il timestamp criptato con la chiave a lungo termine dell’utente che, come sappiamo, si ottiene applicando la string2key alla password in chiaro dopo averci aggiunto l’eventuale salt. Questa volta, il KDC, poiché conosce la chiave segreta dell’utente, tenta di decriptare il timestamp presente nella richiesta e se ci riesce ed il timestamp è coerente, cioè compreso nella tolleranza stabilita, conclude che il richiedente è autentico e il processo di autenticazione continua normalmente.
Si badi bene che la preautenticazione è una policy del KDC e quindi il protocollo non la richiede necessariamente. A livello implementativo, MIT Kerberos 5 ed Heimdal hanno per default la preautenticazione disabilitata, mentre il Kerberos di Windows Active Directory e il kaserver di AFS (che è appunto un Kerberos 4 preautenticato) la richiedono.