La sicurezza di dati e informazioni è un aspetto molto importante all‘interno di sistemi e applicazioni. Java fornisce diversi strumenti per implementare strategie e meccanismi di sicurezza. Un aspetto importante da tenere in considerazione è la necessità di attribuire un‘identità a chi richiede l‘accesso al sistema (autenticazione) e successivamente disciplinarne le azioni una volta riconosciuto (autorizzazione). All‘interno della Java Security technology chi si occupa di questi aspetti è JAAS.
Introduzione
Le problematiche legate alla sicurezza sono sempre un argomento di grande importanza. Nel progetto della Java platform in particolare è stata attribuita una certa considerazione all’aspetto sicurezza e, sin dalle prime versioni, sono stati implementati accorgimenti di natura architetturale allo scopo di garantire la robustezza del codice.
Con la sua evoluzione, la piattaforma è stata migliorata, modificando le sue caratteristicheper garantire maggiore controllo e facilità di gestione della sicurezza; inoltre sono state progressivamente integrate le più diffuse tecnologie, e sono stati inseriti i meccanismi e i protocolli legati alla sicurezza (crittografia, secure communication, authentication e authorization). In questo articolo viene fatta una breve panoramica sulla sicurezza in generale e in Java in particolare, per poi passare alla descrizione delle caratteristiche principali di JAAS, introducendo i concetti fondamentali che saranno approfonditi negli articoli seguenti.
La sicurezza: 4 aspetti fondamentali
Quando si parla di sicurezza delle informazioni, dei dati e delle comunicazioni, senza aver la pretesa di esaurire l’argomento, è possibile considerare 4 aspetti fondamentali [2]: confidenzialità (confidentiality), integrità (integrity), autenticazione (authentication) e autorizzazione (authorization). Discuteremo brevemente questi 4 aspetti per poi affrontare, nel resto dell’articolo, i due di cui si occupa il package JAAS.
La tutela dell’integrità dell’informazione consiste nel fornire la garanzia che i dati, le informazioni non possano essere alterate, corrotte o modificate da utenti non autorizzati. Questo è un rischio a cui possono essere esposti i dati durante la trasmissione. Il processo che garantisce l’integrità dei dati è chiamato validation (validazione). Quando un dato conserva la sua integrità, significa che quest’ultimo non è stato modificato o corrotto. Una tecnica usata per assicurare l’integrità dei dati è il data hashing.
Confidenzialità significa mantenere le informazioni segrete, non bloccando l’accesso alle stesse ma rendendo le informazioni pubblicamente illeggibili. Per proteggere i dati dall’accesso da parte di utenti non autorizzati, è necessario modificare il formato del dato in modo da renderli confidenziali. Questo processo è conosciuto col nome di obfuscation (offuscamento). Gli unici per cui l’informazione risulterà accessibile sono coloro in grado di sbloccare l’accesso al file originale a partire da un file crittografato. Per poter nascondere (crittografare) l’informazione è necessario fare ricorso ad opportuni algoritimi (crittografici) che vengono integrati con l’aggiunta di una chiave segreta (secret key). Questo per evitare che la semplice conoscenza dell’algoritmo permetta anche ad utenti non autorizzati l’accesso all’informazione. La conoscenza dell’algoritmo è importante ma non è sufficiente a compromettere la confidenzialità dell’informazione perche’ per la decodifica è necessario entrare in possesso della secret key.
L’autenticazione è il processo mediante il quale si provvede alla verifica dell’identità dell’utente di un sistema attraverso un insieme di credentials. Le credentials sono richieste dal sistema (ed esibite dall’utente) come prova per poter verificare la sua identità. L’utente può essere una persona fisica, un processo o anche un’altro sistema.
L’insieme di credentials è fortemente dipendente dai requisiti e dalle politiche di sicurezza di un’organizzazione: quelle comunemente usate sono username e password, certificati, smart cards. Le credentials permettono a una parte di riconoscere l’altra. Il riconoscimento può avvenire in molti modi e attraverso diversi strumenti.
Il processo attraverso il quale a un utente viene dato accesso alle risorse del sistema è chiamato autorizzazione. Una volta che l’utente è stato riconosciuto, la sua attività può essere controllata di modo che abbia accesso alle risorse del sistema per le quali detiene i necessari diritti (rights).
Per esempio, dopo essersi autenticato e avere fatto il log-in all’interno di un sistema, l’utente potrebbe avere necessità di eseguire diverse operazioni (per esempio: accedere ai propri dati personali). Per poter svolgere queste operazioni (o avere accesso a determinate risorse) l’utente deve possedere i necessari diritti di accesso (o permission). L’identità dell’utente è stata accertata e l’utente ha avuto accesso al sistema, ma non è detto che possa svolgere tutte le azioni che richiede. A concedergli l’accesso alle risorse “security-sensitive” sarà l’AccessController, dopo aver verificato il possesso delle necessarie permission. L’autenticazione è quindi un pre-requisito per l’autorizzazione.
La sicurezza in Java
La Java Platform è stata progettata sin dall’inizio ponendo una certa attenzione alla sicurezza. Al suo core il linguaggio Java è type-safe e fornisce una garbage collection automatica [3]. Questo permette di ridurre il carico di lavoro sulle spalle del programmatore, rendendo il codice più robusto; riduce inoltre il rischio di esposizione di pericolose falle nella sicurezza. Esiste poi un code verifier che permette di verificare che venga eseguito solo codice Java legittimo.
La Java 2 Platform Security Architecture è stata progressivamente aggiornata e migliorata per ottenere una policy facilmente configurabile a “grana fine” (si usano per default dei file ASCII con dichiarazioni che rispettano una specifica sintassi), una struttura per la gestione del controllo d’accesso facilmente estensibile, l’estensione dei controlli di sicurezza a tutti i programmi Java [3](e non solo, come in precedenza, del solo codice proveniente dall’esterno del sistema, considerando il codice locale sicuro “a prescindere”) sulla base di opportuni requisiti (URL del codice ed eventuali firma e principal di chi ne richiede l’esecuzione).
La Java Security Architecture prevede l’utilizzo di Provider (java.security.Provider) col compito è di fornire i servizi di sicurezza (per esempio i diversi algoritmi di cifratura). I Provider sono inseriti come plug-in nella piattaforma Java attraverso interfacce standard che rendono agevole per le applicazioni ottenere security service senza conoscere niente circa la loro implementazione. È inoltre consentita l’aggiunta di Provider customizzati, che possono estendere i meccanismi di sicurezza già presenti.
È bene sottolineare che il processo di autenticazione gestito dalla JAAS API non sfrutta questa architettura standard ma ha una propria architettura che gestisce il processo interagendo con la classe javax.security.auth.login.Configuration.
Figura 1 – Il framework di autenticazione e i sottostanti plug-in (modificato, da [3])
La Java Security Technology è composta da varie API, tool e implementazioni di algoritmi, meccanismi e protocolli maggiormente usati; in tal modo si forniscono allo sviluppatore gli strumenti necessari per scrivere applicazioni sicure e all’utente e all’amministratore quelli che servono per gestirle in maniera semplice.
Per esempio, per quanto riguarda la crittografia, abbiamo la Java Cryptography Architecture (JCA) che include la Java Cryptographic Extension (JCE). Esistono poi API e tools per la gestione della Public Key Infrastructure (PKI) e per la crittografia.
Nel campo delle secure communication abbiamo per esempio Java Secure Socket Extension (JSSE), Java GSS-API (JGSS), Java SASL API che forniscono le implementazioni per Transport Layer Security (TLS), Secure Sockets Layer (SSL), Kerberos (attraverso la GSS-API) e Simple Authentication and Security Layer (SASL), che permettono di autenticare i peer che comunicano su reti non sicure e proteggere l’integrità e la confidenzialità dei dati trasmessi.
Infine per quanto riguarda l’autenticazione e il controllo di accesso (autorizzazione), che è l’argomento di questa serie di articoli abbiamo la Java Authentication and Authorization Service API (JAAS), che introduciamo alla fine dell’articolo. Di seguito vengono presentati alcuni importanti elementi della sicurezza in Java (e le loro relazioni reciproche).
Il SecurityManager
La classe java.security.SecurityManager è responsabile di determinare se una certa richiesta d’accesso a risorse di tipo security-sensitive possa essere concessa o meno. L’accesso alle risorse è deciso a livello amministrativo e la configurazione è impostata in alcuni file di policy (ASCII per default). I file di policy rispettano un’opportuna sintassi e permettono di attribuire alle classi i permessi di accesso alle risorse (permission). In Java 2 il compito di controllare le permission è generalmente delegato all’AccessController (si può dire che il SecurityManager diventa un wrapper per l’AccessController). In effetti, la classe SecurityManager è mantenuta per ragioni di retrocompatibilità con la versione precedente della piattaforma. Tranne che in alcuni casi (per esempio nelle applet), il SecurityManager non è usato per default: per poterlo avviare occorre compiere alcune operazioni.
È possibile avviarne l’esecuzione per esempio alla linea di comando usando l’opzione
-Djava.security.manager
Un altro modo è instanziarlo nel codice con l’istruzione
System.getSecurityManager();
Nel caso in cui l’accesso alla risorsa non è permesso, il SecurityManager lancia un’eccezione di tipo java.lang.SecurityException.
L’AccessController
La classe java.security.AccessController è l’implementazione di default del SecurityManager e, come già affermato, ha il compito di verificare se il codice che sta tentando di svolgere una particolare richiesta (per esempio l’accesso in lettura a un file ) possiede i necessari diritti (permissions) per completare l’operazione. A seguito di tale verifica, decide se concedere o negare l’accesso richiesto alle risorse. Per fare questo usa il metodo checkPermission().
FilePermission perm = new FilePermission("/temp/testFile", "read"); AccessController.checkPermission(perm);
Nell’esempio riportato (tratto dalla documentazione ufficiale) il metodo checkPermission riceve come parametro una permission di classe java.io.FilePermission, per l’accesso in lettura al file “testFile” nella directory “/temp”, e determina se concedere o meno l’accesso nella modalità indicata a questo file.
L’AccessController può inoltre segnare il codice come “privileged” o ottenere “snapshot” dell’AccessControllerContext. L’AccessControlContext è una classe che incapsula un context e ha un unico metodo: checkPermission() (equivalente al metodo checkPermission() dell’AccessController).
La differenza tra i due è che il metodo dell’AccessControlContext permette o nega l’accesso alle risorse sulla base del context incapsulato e non del thread corrente.
Le Permission
Rappresentano le autorizzazioni all’esecuzione di particolari azioni consentite a una particolare classe o a un dato insieme di classi. Le permission sono concesse valutando la provenienza del codice, l’eventuale certificato (assieme costituiscono il codesource) e chi richiede l’esecuzione del codice stesso (cioè sulla base del principal). La classe Codesource (java.security.Codesource) racchiude l’URL di provenienza del codice più gli eventuali certificati posseduti dal codice. Il principal invece rappresenta l’identità dell’utente all’interno del sistema. Queste permission sono configurate per default in un file ASCII con una sintassi opportuna. Qui di seguito è riportato un esempio.
grant codeBase "file:/home/sysadmin/" { permission java.io.FilePermission "/tmp/abc", "read"; };
In questo esempio viene concesso l’accesso in lettura al file “abc” contenuto nella directory “tmp” al codice localizzato nella directory “/home/sysadmin/” del file system. In questo caso non è richiesto alcun certificato.
Torneremo più avanti sulla sintassi delle permission all’interno del file di policy.
JAAS
Il package Java Authentication and Authorization Service (JAAS) è stato introdotto come package opzionale nella versione 1.3 della Java 2 SDK, Standard Edition e mantenuto nelle versioni successive. JAAS fornisce un meccanismo di autenticazione astratto, che può incorporare un ampio range di meccanismi attraverso un’architettura di tipo pluggable. Offre inoltre al programmatore la possibilità di gestire policy e permission e garantisce una gestione delle politiche di accesso a risorse security-sensitive a “grana fine”. Attraverso JAAS è inoltre possibile l’implementazione del single sign-on.
L’autenticazione con JAAS
La JAAS authentication si basa sull’interfaccia javax.security.auth.spi.LoginModule, che è implementata dai moduli che provvedono a eseguire le autenticazioni, cioè oggetti che implementano l’interfaccia LoginModule, appunto. Questi moduli si inseriscono in modalità plug-in “sotto” l’applicazione per fornire un determinato tipo di autenticazione.
Di seguito vengono descritti i passaggi che si verificano durante il processo di autenticazione.
- Per prima cosa l’applicazione istanzia un oggetto di classe LoginContext (javax.security.auth.spi.LoginContext). Questo oggetto viene istanziato passando al costruttore due parametri: una stringa e un oggetto che implementa l’interfaccia CallbackHandler. La stringa permette al LoginContext di recuperare, a partire dalla classe Configuration, quali LoginModule usare per l’autenticazione. CallbackHandler (javax.security.auth.callback) è un’interfaccia utilizzata allo scopo di fornire un meccanismo di interazione con l’utente per poter raccogliere le informazioni di autenticazione (tipicamente username e password).
- Una volta istanziato il LoginContext, la procedura di autenticazione viene avviata invocando il metodo login().
- Il metodo login() invoca tutti i LoginModule caricati. Ciascun LoginModule tenta di autenticare lo user (o il service). Un Subject (javax.security.auth.Subject) raccoglie un insieme di informazioni collegate a una singola entità come, per esempio, quelle relative alle identità del subject.
- Una volta che l’autenticazione è avvenuta, i LoginModule associano principal e credentials al Subject.
- Il LoginContext ritorna lo stato di autenticazione all’applicazione.
- Se l’autenticazione ha avuto successo, l’applicazione è in grado di ottenere un oggetto Subject (con associati i principal e le credentials) dal LoginContext.
Utilizzando un’opportuna sintassi, il file di configurazione poi specifica i LoginModule da utilizzare. Ne riportiamo un esempio:
MyApplication { com.sun.security.auth.module.Krb5LoginModule required; };
Nell’esempio è riportata una entry del file di configurazione per la JAAS authentication. In questo file dichiariamo che l’applicazione “MyApplication” richiede per l’autenticazione un LoginModule di classe com.sun.security.auth.module.Krb5LoginModule (cioè usiamo Kerberos) e che questo LoginModule è “required”, cioè necessario al fine di portare a termine il processo di autenticazione. Esistono altri flag (requisite, sufficient e optional) che hanno il compito di controllare il comportamento del processo di autenticazione (soprattutto nel caso in cui siano presenti più LoginModule). Torneremo in seguito sulla sintassi dei file di configurazione di questo tipo.
Questo evidenzia la natura pluggable e stacked di JAAS. Infatti, il tipo di LoginModule (e altre opzioni) sono determinati proprio dalle dichiarazioni contenute in questo file. In tal modo è possibile sostituire il LoginModule (cioè cambiare i meccanismi di autenticazione) senza dover per questo modificare il codice dell’applicazione. Inoltre è possibile specificare diversi LoginModule (come in uno stack appunto) per fornire, qualora necessario, una combinazione di metodi di autenticazione (natura stacked di JAAS).
L’autorizzazione con JAAS
L’autorizzazione in JAAS estende la Java Security Architecture esistente che usa, come abbiamo visto, una politica di sicurezza per specificare quali diritti d’accesso sono garantiti al codice in esecuzione. Tale architettura, introdotta nella piattaforma Java 2, è code-centric. Ciò significa che le permission sono garantite sulla base di caratteristiche del codice: da dove proviene (quale URL è associata al codice) e se sono presenti certificati (e in tal caso in base a chi ha firmato il codice). Un esempio di tale tipo di autorizzazione (senza certificati) è stato riportato nel paragrafo sulle permission.
L’autorizzazione con JAAS integra questo tipo di controllo aggiungendovi un controllo sull’utente (l’approccio user-centric). Le permissions possono essere garantite sulla base non solo di quale codice è in esecuzione ma anche in funzione di chi ne ha richiesto l’esecuzione.
Quando un’applicazione usa l’autenticazione JAAS per autenticare un utente (persona fisica o servizio), viene creato, come risultato di questa operazione, un Subject. Il Subject racchiude al suo interno i principal e le credentials (private e/o pubbliche).
L’esigenza di provvedere a un altro oggetto (il subject appunto) oltre al principal, nasce dalla considerazione che un utente, nell’interazione con i sistemi, possiede varie identità: all’interno di un sistema l’utente può essere individuato da un nickname (“MarioRossi”) e avere come credentials la coppia username e password; ma un altro sistema può identificarlo attraverso il codice fiscale, e richiederne l’autenticazione attraverso un’altra maniera. A ciascuna di queste identità corrispondono principal e credentials, e il Subject è la classe che racchiude questi dati. Un utente associato a un Subject sarebbe in grado di autenticarsi in più sistemi (quelli per i quali detiene principal e credentials).
Una volta autenticato l’utente, siamo in grado di ottenere dal LoginContext il subject con associati i principal e le credentials. Una volta ottenuto il subject sarà possibile eseguire delle operazioni con il subject autenticato. Per fare questo usiamo i seguenti metodi static:
public static Object doAs(final Subject subject, final java.security.PrivilegedAction action); public static Object doAs(final Subject subject, final java.security.PrivilegedExceptionAction action) throws java.security.PrivilegedActionException;
Entrambi questi metodi prima associano lo specifico subject con l’AccessControlContext del Thread corrente e quindi eseguono la action. La action è un’oggetto che implementa l’interfaccia java.security.PrivilegedAction, che possiede un unico metodo:
public Object run()
In questo modo otteniamo che l’azione venga eseguita dal subject (cioè da chi possiede opportune credentials).
Conclusioni
Abbiamo visto una rapida carrellata dalle problematiche inerenti alla sicurezza e come vengono affrontate all’interno della Java Platform. Abbiamo preso confidenza con alcuni componenti che giocano un ruolo fondamentale nella gestione della sicurezza in Java e in JAAS in particolare. Nei prossimi articoli approfondiremo le tematiche dell’autenticazione e dell’autorizzazione in JAAS.
Riferimenti
[1] JavaTM 2 Platform Security Architecture, Sun Microsystem
http://java.sun.com/javase/6/docs/technotes/guides/security/spec/security-spec.doc.html
[2] Rich Helton and Johennie Helton, “Java Security Solutions”, Wiley Publishing, Inc. 2002
[3] Java™ Security Overview, Sun Microsystem
http://java.sun.com/javase/6/docs/technotes/guides/security/overview/jsoverview.html
[4] JavaTM Authentication and Authorization Service (JAAS) – Reference Guide – Sun Microsystem
http://java.sun.com/j2se/1.4.2/docs/guide/security/jaas/JAASRefGuide.html