MokaByte
Numero 25 - Dicembre 98
|
|||
|
sicurezza con il JDK 1.2 |
||
Giovanni Puliti |
|
||
Con il rilascio del nuovo JDK 1.2, i progettisti della Sun hanno completamente cambiato il modello relativo alla gestione della sicurezza. Adesso è possibile implementare soluzioni più flessibili e potenti che in passato, vediamo come... Se provate a
chiedere ad una persona con una cultura informatica media, se ha sentito
parlare di Java e perché, sicuramente vi risponderà di sì,
e menzionerà una parola magica: "portabilità". In effetti
questo aspetto è sicuramente quello che ha fatto di Java un linguaggio
innovativo, ed al tempo stesso lo ha proposto come la risposta ad uno dei
maggiori problemi del 90% degli sviluppatori.
Modello on-off Prima dell'introduzione
del nuovo modello, era il Security Manager (SM) ad eseguire un controllo
su tutte le operazioni eseguite dalla applet proibendo quelle vietate.
In base a tali restrizioni una applet non poteva collegarsi con server
differenti da quello di provenienza, non poteva scrivere sul server, non
poteva leggere né scrivere sul client.
Il modello a permessi variabili La limitazione
maggiore del modello on-off è la scarsa granularità ottenibile:
o l'utente non può fare niente o può fare tutto. In scenari
sempre più orientati all'interconnettività e alla gestione
di architetture distribuite, poter disporre di un sistema maggiormente
flessibile, personalizzabile e configurabile dinamicamente, è una
esigenza sempre più sentita. Può essere utile diversificare
il tipo di operazioni che una applet può eseguire in base al proprietario
della stessa (cioè da dove la si è scaricata), oppure in
base al tipo delle operazioni (su quale risorsa si intende lavorare). Praticamente
un sistema molto simile alla gestione degli utenti e dei permessi che si
trova in Unix. Il modello della sicurezza del JDK 1.2 risponde proprio
a questa esigenza: è possibile adesso creare tante tipologie di
permessi a seconda delle esigenze potendo controllare nei minimi dettagli
tutti gli aspetti coinvolti. Vediamo brevemente come è strutturato
il nuovo scenario. Il tutto parte dal lato client dove si può specificare,
in base alla provenienza della applet, il tipo di operazioni che essa potrà
eseguire in locale, e su quali risorse. Non si tratta quindi di un meccanismo
on-off, ma della possibilità di creare una politica molto più
dettagliata di permessi. Al posto del SM adesso troviamo l'Access Controller
(AC), il cui compito è lo stesso del suo predecessore, ma che offre
la possibilità di personalizzare gli accessi. In realtà il
SM continua ad essere presente per mantenere la compatibilità all'indietro
con le applicazioni basate sul JDK 1.1: in questo caso però esso
esegue solo una interfaccia per l'AC che esegue in background tutte i controlli
del caso. In Figura 1 è riportata l'attuale configurazione del sistema.
La possibilità di modificare la gestione dei permessi consente di
cambiare completamente la politica della sicurezza semplicemente editando
un file di configurazione (anche in runtime), cosa possibile in precedenza
solo sostituendo completamente il SM (operazione non facile e non sempre
possibile).
Il funzionamento dell'AC si basa sui seguenti concetti: Provenienza del codice: specifica da dove il codice è stato scaricato se si tratta di un host remoto, o caricato se proviene dal file system locale; Permesso: una serie di permessi disponibili che verranno associati alle singole operazioni eseguibili sulla macchina; Politica di sicurezza: un insieme di associazioni che specificano le operazioni che possono essere eseguite e da chi; Dominio di protezione: un insieme di privilegi (o permessi) associati ad un insieme di programmi Java (applet o applicazioni) con la stessa provenienza, e firmati elettronicamente da utenti appartenenti allo stesso gruppo. Vediamo di entrare nel dettaglio dei singoli elementi per capirne il significato ed il funzionamento. Permessi L'elemento base su cui l'AC basa il suo funzionamento è l'oggetto (inteso come entità) Permesso, che corrisponde alla classe java.security.Permission. A tale classe sono attribuiti due significati distinti: quando un oggetto Permission è associato ad una classe, esso rappresenta l'insieme di operazioni che sono state garantite alla classe stessa; altrimenti esso consente di sapere se si possiedono i permessi necessari per poter eseguire una certa operazione. Il possedere una classe di permesso di accesso a file non significa che si possa realmente accedere al file, ma rende noto se è possibile farlo. Una classe Permission ha tre proprietà fondamentali: Un tipo: il tipo di permesso che si intende specificare (accesso a file o a socket). Le API di Java mettono a disposizione 11 tipi di permessi, che vanno dall'accesso a file (java.io.FilePermission), a permessi legati alla possibilità di accedere a risorse di rete via socket (java.net.SocketPermission), a classi legate a permessi di esecuzione di operazioni su thread e simili (java.lang.Runtimecission); Un nome: anche se non è obbligatorio e non è stata definita una regola precisa di naming, in genere si cerca di mantenere una relazione piuttosto stretta fra il nome e la sua area di interesse. MyFilePermission piuttosto che MyPermissioXYZ nel caso di permessi su file; Una serie di azioni: in funzione della tipologia di permesso, è specificata una serie di operazioni che è possibile eseguire. Ad esempio, creando ex-novo un oggetto FilePermission possiamo pensare di fornire azioni come la scrittura o lettura del file, mentre in altri casi tali azioni non avrebbero senso. Da notare che
la classe Permission è astratta, e che quindi non è utilizzabile
direttamente: dal punto di vista del programmatore generalmente essa serve
principalmente per la definizione di permessi particolari. Dispone di alcuni
metodi di servizio che però non sono utilizzati nella programmazione
generica.
La class Policy Dopo aver definito
un set di permessi, si deve consentire all'AC di dedurre quali di essi
devono essere applicati e a quali codici: la coppia AC+Policy svolge il
compito che precedentemente era assolto dal SM.
Il passo successivo è quello di creare una serie di accoppiamenti fra programmi Java e permessi, in modo da definire una politica più o meno restrittiva. La java.security.Policy provvede al caricamento delle informazioni che portano alla politica di permessi. La classe è package-protected per cui non può essere acceduta direttamente in fase di programmazione: non è possibile cioè modificare il meccanismo di lettura dei permessi ma solo sostituirlo in toto. Si potrebbe in teoria creare un meccanismo ex novo, ma è un compito non banale, e nemmeno estremamente necessario. La configurazione scelta viene poi memorizzata in un file editabile e modificabile, in modo da garantire la massima flessibilità in ogni momento. Se ad esempio vogliamo che un programma possa leggere una singola directory o un solo campo di una tabella basta inserire tali informazioni in tale file. Si tratta del JAVAHOME/lib/security/java.security. Nel file java.security vi è un esempio di come può essere strutturato tale file. Domini di Protezione Un dominio di protezione rappresenta l'insieme di tutti i permessi che una certa classe (programma client sulla JVM) può eseguire. Un dominio è rappresentato materialmente dalla classe java.security.ProtectionDomain, il cui costruttore è public ProtectionDomain(CodeSource cs, Permission p)Associare un ProtectionDomain ad una certa classe, permette di specificarne il sito di provenienza (specificato dal nome del sorgente cs), la firma elettronica (sempre specificata in cs), ed il set di regole (date da p) alle quali deve sottostare. Non tutte le classi che vengono utilizzate da una applicazione, hanno associato un dominio di protezione: è il caso delle classi di sistema, che hanno un dominio per default detto system protection domain. La Classe Access Control in azione Adesso abbiamo
tutti i pezzi per poter passare al soggetto principale di tutto il sistema,
ovvero l'Access Controller. Esso corrisponde alla classe java.security.AccessController,
classe non accessibile direttamente dato che il costruttore è protetto
e possiede una serie di metodi statici molto utili all'applicazione client
per determinare a quali restrizioni è soggetta. In tal senso il
metodo fondamentale, il checkPermission(), prende come parametro un permesso
e determina se la classe è autorizzata a procedere. Nel file AccessControllerTest.java,
presente sul dischetto allegato, è riportato un esempio molto semplice
di come possa essere utilizzata tale tecnica.
Supponiamo il
caso di una applet in esecuzione all'interno dell'appletviewer: dato che
l'appletviewer stesso è basato su thread, è suo il primo
metodo nello stack. Successivamente, al momento dell'istanziazione della
applet, viene chiamato il thread della classe Applet: abbiamo quindi due
thread in esecuzione, il cui dominio di protezione è quello di
sistema. Il controllo passa poi al metodo init() della applet, ed a questo
punto viene effettuato il controllo del dominio di provenienza.
Tipicamente una applet sarà costituita da classi provenienti da un solo host ed appartenenti allo stesso autore, per cui il problema della coesistenza di domini differenti è praticamente molto rara. Inoltre il poter scaricare classi da più server differenti è una prerogativa possibile in una applet, ma solo se si è predisposto un permesso apposito. Permessi Speciali Abbiamo visto quindi come sia possibile controllare le azioni che una applicazione Java intende eseguire sia per mezzo del restrittivo e rigido Security Manager, sia del più flessibile e personalizzabile Access Controller. Nel primo caso l'applicazione, o non può fare praticamente niente in locale, oppure ha pieno possesso della macchina (virtuale ma anche e soprattutto reale); nel secondo caso invece è possibile specificare a priori una certa politica di permessi ed associarla ad un determinato codice. In entrambe le situazioni però il carattere per così dire della VM è predeterminato al momento dell'istanziazione e non può essere modificato in fase di run. Questa può essere una limitazione in quei casi in cui, pur volendo mantenere un alto livello di sicurezza, si desideri abbassare la guardia per un breve lasso di tempo. Il modello AC permette di risolvere il problema per mezzo della coppia Tutto il codice inserito fra questi due metodi ha il potere di agire con i permessi della classe che ha chiamato beginPrivileged (classe contenitore). Tutte le chiamate interne al blocco a metodi di classi possono agire con maggiore libertà. Ovviamente questo meccanismo non porta nessun particolare vantaggio per quel codice costituito da semplici sequenze di operazioni non facenti riferimento a classi esterne: esso infatti viene in ogni caso eseguito con il permesso della classe contenitore. Analogamente la tecnica è inutile per le chiamate a metodi di classi aventi diritti più ampi della classe contenitore, proprio per la regola dell'intersezione. Vediamo un esempio: Come si può notare la coppia begin/end deve essere inserita all'interno di una coppia try-catch e deve essere prevista anche la clausola finally: si deve impedire infatti che una generica eccezione interrompa l'esecuzione del programma lasciando in sospeso la begin. Per questo motivo è necessario l'uso della try ed, analogamente, è molto importante la finally che permette di chiudere il blocco privilegiato. Per chi dimentica sempre la porta di casa aperta, nonostante abbia appena comprato una porta blindata di ultimo tipo, c'è una notizia confortante: tutta la gestione di una begin/end avviene in modo molto simile ad una transazione. Sono infatti previsti meccanismi automatici di timeout e di controllo delle referenze ad oggetti esterni non più utilizzati (finiti nel cestino del garbage collector) che garantiscono in ogni caso la chiusura della transazione. Passiamo alla pratica… Dopo una dose
massiccia di teoria, qualcuno di voi si starà chiedendo come mettere
in pratica quanto affrontato fino ad adesso.
genera una chiave pubblica associandovi un alias dell'autore delle chiavi (Criptor) e una password (myPassGiovanniXYZ). Le chiavi vegono memorizzate in un oggetto java.security.KeyStore il quale a sua volta viene inserito in un file criptato .keystore, situato nella home directory dell'utente, e protetto da una password che il keytool richiede all'utente insieme ad altre informazioni sull'identità dello stesso. A questo punto deve essere generato un file .jar contenente i .class della applet (supponiamo per brevità solo il file MyApplet.class): Si deve quindi firmare l'applet contenuta nel jar per mezzo di un comando tipo le due password "pippo" e "myPassGiovanniXYZ" servono rispettivamente per accedere al file .keytool (che, come appena detto, è protetto), ed alla chiave privata in esso contenuta. Conseguentemente di una azione di questo tipo, adesso il file jar MySignedApplet, contiene, oltre al MyApplet.class relativo alla applet ed il manifest.mf, anche due file Criptor.sf e Criptor.sda, che serviranno al client per autenticare l'autore della applet e per controllarne l'integrità.A questo punto deve essere preparata la pagina Html in modo che preveda questa nuova situazione: Il client però deve ottenere la chiave pubblica di Critpor in modo da poterne verificare la firma. Per consegnare tale chiave al client, si può esportare dall'archivio personale di Criptor in un file (Criptor.cert), per mezzo del comando: Questa operazione di fatto esporta la chiave di Criptor dal suo archivio personale nel file .cert. Operazione del tutto analoga ma opposta, deve essere fatta dal client che dovrà inserire tale chiave in un archivio locale (un file .keystore) per mezzo di: associandola ad un alias (AliasCriptor). Vediamo infine come sia possibile per il client dare maggiore libertà d'azione all'applet ospite: esso può inserire in un file denominato ad esempio "MyPolicies.txt": Questo grant rilascia i privilegi d'accesso in lettura su tutto il file system del client e directory /data/criptor a quelle applicazioni firmate da Criptor (identificato nel db locale per mezzo dell'alias "AliasCriptor"). Per attivare il proprio file di policy quando si utilizza l'appletviewer si può ad esempio utilizzare un comando del tipo Come si è potuto osservare si tratta di eseguire una serie di operazioni piuttosto lunga, ma tutto sommato semplice. È bene comunque tenere presente che il modello a sandbox continuerà ad essere valido (anche se filtrato da questo nuovo), per cui utilizzando la JVM 1.2 non è necessario per forza di cose utilizzare firme elettroniche o file di permessi. Il modello AC sarà in ogni caso sempre presente ed attivo, ma entrerà in gioco in maniera attiva solo quando richiesto esplicitamente. Conclusioni Abbiamo visto
come il nuovo modello di gestione della sicurezza rappresenta sicuramente
un notevole passo in avanti rispetto al pur lodevole meccanismo del Security
Manager.
Bibliografia [1]
"The Java Security", di Scott Oaks Ed. O'Reilly.
|
MokaByte Web 1998 - www.mokabyte.it MokaByte ricerca nuovi collaboratori. Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it |