La security di Java


Home Page | Commenti | Articoli | Faq | Documenti | Ricerca | Archivio | Storie dalla Sala Macchine | Contribuire | Login/Register

Esattamente come introduce nuove idee nella programmazione, Java introduce anche nuovi problemi relativi alla sicurezza. Come assicurarsi che un oggetto ottenuto tramite RMI non e' "alterato" lungo la rete prima di essere consegnato al client che lo richiede? Come assicurarsi che un dispositivo JINI-enabled non e' il bersaglio di un attacco DoS?

Le Security API di java sono progettate appositamente per risolvere questo tipo di problemi.

La sicurezza in Java e' gestita da una ridda di piccoli componenti, che sono distribuiti in svariati packages, tutti questi componenti devono riunirsi e cooperare per comporre il giusto puzzle.

Tutte le applicazioni Java sono interessate da questa architettura, non e' importante se la vostra applicazione ha a che fare con crittografia, autenticazione o altro, il "nucleo" del modello di sicurezza e' sempre li', anche se la vostra applicazione lo ignora.

Il modello di Java si basa su Policy e Permission, e' rivolto a risolvere problemi di integrita' (il codice non puo' fare cose che l'autore o l'utente non consentono) e, in misura minore, problemi di DoS e segretezza

Dato che e' richiesto che lo sviluppatore in persona affronti il concetto di sicurezza, applicazioni che devono svolgere operazioni considerate "a rischio" non possono essere sviluppate senza che il programmatore faccia qualche cosa di specifico. Se una applicazione cerca di fare qualche cosa per la quale non e' autorizzata, si otterra' un bell'errore.

In soldoni, una Permission e' semplicemente un modo di dire che qualchecosa puo' fare una certa azione su un certo oggetto.

Questo modello e' derivato direttamente dal meccanismo di protezione di molti sistemi operativi (in particolare Unix): un utente (cosa) puo' avere dei permessi per accedere un particolare file (l'oggetto) e, per esempio, leggerlo (l'azione).

Ecco tutta l'architettura in un paragrafo: del codice specifico (descritto come CodeSource) puo' effettuare una certa azione su un certo oggetto (descritto come una Permission).
Oggetti di tipo Policy gestiscono le relazioni tra il CodeSource e le Permission.

Le Permissions si applicano su classi, non oggetti, dire che la classe Xyz puo' fare una certa cosa implica che tutti le istanze di quella classe possono farla, indipendentemente da chi le ha istanziate.

Per fare le cose ancora piu' semplici: le Permission dicono cosa una classe puo' fare, e mai cosa non puo' fare.

La relazione tra il CodeSource e la Permission e' contenuto in una Policy, ed e' disponibile ancora prima che l'applicazione sia considerata dalla Virtual Machine. Consideriamo il ciclo di vita di un'oggetto:

Quando un'oggetto e' istanziato, la classe deve essere prima definita, basandosi sul CodeSource della classe, e' quindi collegato un set di Permissions che una Policy ha preparato, la VM e' a conoscenza della Policy al suo avvio.

Quando l'istanza e' successivamente "azionata" attraverso uno dei suoi metodi, un SecurityManager puo' verificare le sue Permissions a runtime, richiamando una eccezione se la classe manca della necessaria Permission.

Questa e' una semplificazione notevole di cio' che avviene, "sotto al coperchio", le cose sono un po' piu' complesse. Per esempio, il ClassLoader raggruppera' tutte le Permissions in un ProtectionDomains e le associera' alla classe che sta' per essere caricata invece di collegare la classe direttamente con una collection. Inoltre (come potete capire da soli), un ClassLoader non e' altro che un'altra classe a sua volta caricata da un ClassLoader...

In effetti c'e' una "catena" di ClassLoaders che risale nella gerarchia fino al ClassLoader della VM stessa, che e' completamente inaccessibile dalle altre classi caricate in seguito (viene ritornato come null). E non abbiamo ancora parlato della complessita' di verificare ogni classe a runtime per le necessarie Permissions.

Come sviluppatori, solitamente non ci dobbiamo preoccupare di questi dettagli, la VM si occupa del lavoro sporco.

Piu' importante e' che questo modello e' estensibile, possiamo creare le nostre Permissions, richiedere controlli quando e' necessario nell'applicazione e creare Policy specializzate. Inoltre possiamo alterare le Policy per i singoli utenti, modificando le capacita' del codice a runtime senza modificare il codice.

Le Policy sono relativamente semplici, esse collegano il codice con una o piu' Permissions in un formato che e' piu' o meno key=value. Il contenuto tuttavia vale una visita guidata.

CodeSource, definito in java.security.CodeSource, identifica i files class per ragioni di sicurezza. Non confondetelo con il codebase, che e' la proprieta' usata per descrivere la posizione di "root" di un Applet o di una applicazione.

Esistono vari "tipi" di Permission, ognuno si rivolge ad uno specifica "classe" di funzioni, per esempio, FilePermission sono relative a funzioni di I/O sui file. I possibili oggetti associati alla Permission devono essere ragionevolmente collegati al suo tipo (esempio: FilePermission sono associate ad oggetti di tipo File).

Le azioni che queste Permission consentono saranno (a loro volta) ragionevolmente collegate con gli oggetti a cui si riferiscono (FilePermission consentira' azioni come read e write).

Una parola relativa alle azioni: non tutte le Permission le hanno. In effetti, molte delle sottoclassi di BasicPermission non ne fanno uso. RuntimePermission (per esempio) elenca un possibile numero di oggetti che possono essere "eseguiti", ma non ha nessuna azione.

L'oggetto stesso, tuttavia, implica una azione. Per esempio la RuntimePermission che ha come oggeto createClassLoader, implica che l'oggetto e' ClassLoader e l'azione e' create.

Riferirsi alla documentazione per ulteriori informazioni su questo: http://java.sun.com/products/jdk/1.2/docs/guide/security/Permissions.html.

Nell'implementazione di default di Java 2, e' possibile mettere le informazioni relative alle Policy nello stesso file di testo in cui la Virtual Machine crea gli oggetti di Policy.

Se siete interessati a creare un nuovo security provider, potete implementarvi il vostro sistema di archiviazione delle Policy usando il vostro database preferito, una SmartCard o un Bean. Per gli scopi di questo articolo, noi ci concentreremo sull'implementazione standard.

Di default, Java usa un file standard per generare la propria Policy centrale. Questa e' disponibile per ogni Virtual Machine, quindi il suo data source e' sempre lo stesso: $(JAVA_HOME)/jre/lib/security/java.policy. Potete creare dei file di policy addizionali seguendo la stessa sintassi.

Detto in due parole, il file specifica cosa, un certo codice (da una certa posizione e magari firmato con un certo certificato) puo' fare su determinati oggetti.

La posizione da cui il codice opera/viene scaricato ed il certificato sono attributi opzionali, se non vengono inclusi l'autorizzazione verra' applicata a tutte le copie del codice indicato.

Possono anche essere usati dei caratteri "jolly" per specificare il codice, per rendere piu' semplice la struttura del file.

Una volta che avete preparato il vostro Policy File, potete integrarlo in vari modi. Per collegarlo "dinamicamente", includete il file come parametro di runtime nel richiamo della vostra applicazione:


-Djava.security.policy=[path/nome/del/file]

Per esempio:


java ­Djava.security.policy=/foo/home/myperms.policy Foo

esegue Foo.class con le policy specificate nel file "myperms.policy".

E' anche possibile mettere il file nella directory di un utente e renderlo disponibile per il codice eseguito da quell'utente, questa e' una tecnica utile se si vuole differenziare le possibilita' di diversi utenti.

Questo modo e' possibile perche' java.security specifica directory utenti come una posizione "valida" per un file di Policy. Naturalmente questa direttiva puo' essere rimossa per evitare che gli utenti alterino le proprie autorizzazioni.

Dulcis in fundo, potete rendere le vostre policy disponibili a tutti in una VM creando una nuova entry nel file java.security.

Questo ha lo svantaggio di richiedere una procedura di installazione separata se la vostra applicazione viene portata da una piattaforma all'altra, ma puo' essere utile se la vostra applicazione non viene reinstallata di frequente.

Per installare una policy in questo modo date un'occhiata alla seguente linea del java.security:


policy.url.1=file:${java.home}/lib/security/java.policy
policy.url.2=file:${user.home}/.java.policy

Dovrete aggiungere una linea simile specificando il percorso del vostro file di policy.

Un modo molto semplice per creare e modificare Polocies e' il tool policytool, che si trova nella $(JAVA_HOME)/bin della vostra installazione. Si tratta di un tool grafico, quindi richiede un'ambiente grafico per funzionare (X o Windows). Dato che questo tool "nasconde" il meccanismo interno del Policy file, e' meglio non diventarne troppo dipendenti.

Anche se gli sviluppatori solitamente non si preoccupano troppo delle routine usate durante un controllo di Permissions, capire come funziona il meccanismo puo' aiutare ad implementare un meccanismo migliore.

Gli oggetti importanti in questo controllo sono: SecurityManager, AccessController e AccessControllerContext.

Per prima cosa, SecurityManager entra in scena, quando un metodo richiede una permission per essere eseguito correttamente, il metodo recupera un SecurityManager e richiama il suo checkPermission().

E' possibile aggiungere protezione e controllo alle vostre applicazioni semplicemente aggiungendo una chiamata esplicita al SecurityManager nel vostro codice:


SecurityManager sm = System.getSecurityManager();
If (sm != null) sm.checkPermission(perm);
// if the permission isn't granted, an AccessControlException
// will be thrown.

Se volete utilizzare dei controlli di esecuzione piu' stringenti di quelli standard usati da Java, dovete creare le vostre sottoclassi e richiedere i security check in ogni metodo richiamato. Per esempio se volete verificare/evitare la creazione di stringhe troppo grandi, create la vostra sottoclasse di String e richiamate i controlli necessari nei metodi che volete proteggere. Teoricamente potete forzare un check prima di ogni metodo nella vostra applicazione.

SecurityManager tuttavia, non fa' altro che passare le Permission all'AccessController.

L'AccessController e' quello che fa' effettivamente il lavoro. E' un'arnese piuttosto complesso, che si appoggia su un numero notevole di chiamate native, ed e' difficile rendere appieno cosa fa in poche parole.
Essenzialmente, quando controlla una Permission, l'AccessController prende un'istantanea dello Stack corrente, lo incapsula in un AccessControllerContext che utilizza metodi nativi per recuperare un'array di classi che rappresentano lo stack corrente con tutti i suoi thread. Quindi verifica le Permissions (nella forma di ProtectionDomains) associati con ogni classe dello stack.

Se qualche classe (anche una sola), manca della permission in fase di test, un'eccezione viene eseguita. Altrimenti il metodo ritorna senza dire nulla.

In alcuni casi pero', si vuole "cambiare le regole", anche solo per una classe e solo per un caso. Questo e' fatto usando doPrivileged.

Ipotizziamo che Admin.class abbia i permessi per leggere una stringa "segreta", mentre User.class non ce li abbia (e non debba averceli).

Nonostante cio', in certe condizioni un'oggetto User deve eseguire dei metodi tipici di Admin che richiedono il valore di tale stringa. E' possibile fornire tali privilegi senza fornire ad User l'accesso permanente alla stringa usando doPrivileged().

Nota: doPrivileged ritorna sempre un'oggetto (se ritorna qualche cosa).

A giudicare da vari post su svariate mailing list di Java, decidere quali privilegi aggiudicare al codice comporta parecchi problemi. Questo puo' essere vero se state portando del codice da Jdk 1.1. a Jdk 1.2 o superiore, che improvvisamente richiede delle permission per eseguire dei metodi che prima non li richiedevano.

Non esiste nessun tool (AFAIK) per generare automaticamente le policy necessarie per del codice specifico, la cosa migliore e' quella di pianificare con le Permission gia' in testa fin dall'inizio, e progettare il codice in accordo a questo.

Tenere bene a mente che, senza doPrivileged(), ogni singola classe in un thread richiede le Permission perche' Java sia contento. Se un metodo in A richiama un metodo in B che richiama un metodo in C che richiede una certa permission, tutte e tre le classi devono avere tale Permission perche' possano funzionare.

L'unica applicazione veramente sicura e' quella che non sta' funzionando, in ogni altro caso non esiste una cosa come "codice sicuro", ma utilizzando al meglio i meccanismi di protezione ed autorizzazione del codice forniti da Java, e' possibile ridurre di molto i pericoli insiti.


I commenti sono aggiunti quando e soprattutto se ho il tempo di guardarli e dopo aver eliminato le cagate, spam, tentativi di phishing et similia. Quindi non trattenete il respiro.

Nessun messaggio this document does not accept new posts

Precedente Successivo

Davide Bianchi, lavora come Unix/Linux System Administrator presso una societa' di "sicurezza informatica" (aka: $networkgestapo) di Haarlem. Contatti: mail: davide AT onlyforfun.net , Jabber: davideyeahsure AT gmail.com,

Volete contribuire? Leggete come!.
 
 

Il presente sito e' frutto del sudore della mia fronte (e delle mie dita), se siete interessati a ripubblicare uno degli articoli, documenti o qualunque altra cosa presente in questo sito per cortesia datemene comunicazione (o all'autore dell'articolo se non sono io), cosi' il giorno che faccio delle aggiunte potro' avvisarvi e magari mandarvi il testo aggiornato.


Questo sito era composto con VIM, ora e' composto con VIM ed il famosissimo CMS FdT.

Questo sito non e' ottimizzato per la visione con nessun browser particolare, ne' richiede l'uso di font particolari o risoluzioni speciali. Siete liberi di vederlo come vi pare e piace, o come disse qualcuno: "Finalmente uno dei POCHI siti che ancora funzionano con IE5 dentro Windows 3.1".

Web Interoperability Pleadge Support This Project
Powered By Gort