Permission

Besoin

Autoriser un acteur donné à effectuer une opération sur une ressource.

Implémentation

ACL/Permissions

A l'image de la hiérarchie des exceptions, les permissions sont représentées par une classe racine, java.security.Permission (classe abstraite à ne pas confondre à l'interface obsolète java.security.acl.Permission), et diverses classes dérivées représentant les permissions prédéfinies de la plate-forme Java 2. Comme pour les exceptions également, il est possible de créer ses propres permissions au travers de classes dérivant de cette racine ou de l'une des permissions prédéfinies.

Il existe effectivement des permissions relatives à chaque aspects de la plate-forme Java (système de fichiers, réseau, interface graphique, introspection...) dont les classes se trouvent dans les packages concernés (java.io, java.net, java.awt, java.lang.reflect...). On peut trouver une liste complète des permissions possibles et des risques encourus à les autoriser dans la documentation du JDK.

Comment fixer des permissions ?

Il est possible de fixer des permissions de deux manières :

  • par programmation
  • dans un fichier définissant une politique de sécurité

Notons toutefois que la programmation sera dans certains cas à préférer aux fichiers de politique de sécurité, dans la mesure où certains types de permissions sont dépendantes de la plate-forme, comme les permissions liées au système de fichier, dont le paramétrage peut avoir à spécifier des séparateurs de fichiers spécifiques ("/" sous Unix, "\" sous Windows, ":" sous MacOS...).

Concrètement, les permissions sont définies par :

  • un type (une classe de permission, FilePermission par exemple)
  • un nom cible désignant la donnée concernée par la permission (le nom d'un fichier, d'une machine, une propriété...)
  • une action autorisée sur cette cible (lire, écrire...), fonction du type de la permission. Notons que nombre de permissions ne spécifient pas d'action, puisque définissant une permission "tout ou rien" (les actions seraient "autorise" et "interdit" mais la seule présence de la permission signifie "autorise"). Comme le seul nom cible suffit à désigner ces permissions, on les appelle permissions "nommées".

Une permission définie par ces trois composantes peut ensuite être accordée à un acteur identifié par son origine de code, qui encapsule l'URL de la provenance du code (y compris pour du code local) et/ou un certificat (il est donc possible de définir des permissions pour des acteurs ne disposant pas de certificat).

Toute application Java (y compris les applets) aura toujours le droit de lire le système de fichier à partir du niveau défini par l'URL de leur origine de code (sous-répertoires y compris donc). Elle n'aura jamais besoin d'une quelconque permission pour cela. Ne placez donc pas de ressources sensibles (données confidentielles, exécutables) dans le répertoire d'origine ou sous le répertoire d'origine d'un code suspect.

Par exemple :

<strong>grant codeBase</strong> "file:${java.home}/lib/ext/-" {<br> <strong>permission java.security.AllPermission</strong>;<br> };

autorise toutes les entités chargées à partir de l'URL file:${java.home}/lib/ext à effectuer toutes les actions possibles, et :

<strong>grant signedBy</strong> "Duke", <strong>codeBase</strong> "https://buwin/plugintest" {<br> <strong>permission</strong> <strong>java.io.FilePermission</strong> "tmpFoo", "write";<br> };

autorise tout code signé par Duke et provenant de l'URL https://buwin/plugintest à écrire le fichier tmpFoo .

Chaque politique peut être associée aux opérations d'une plate-forme Java (JDK ou JRE/plugin), ainsi qu'à un utilisateur particulier de cette plate-forme.

Cette politique est définie dans un fichier, spécifié par le fichier de sécurité de Java 2, java.security dans le sous-répertoire lib/security/ de votre plate-forme Java.

On trouvera par défaut dans ce fichier que la spécification de la politique de sécurité doit être lue depuis le fichier java.policy dans le même sous-répertoire, ainsi que dans le répertoire HOME de l'utilisateur courant :

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

Certificate, X509Certificate, CertificateFactory

Contrôleur d'accès

Le rôle du contrôleur d'accès est donc à terme de remplacer le Gestionnaire de Sécurité. Il s'agit d'une classe statique (non-instantiable), contrôlant les accès aux ressources en fonction du Contexte de Sécurité courant.

Exécution privilégiée

Il est possible pour une classe privilégiée (disposant d'une permission donnée) de transmettre momentanément son contexte de protection (les permissions qui lui sont accordées) à une autre classe non privilégiée.

Il convient cependant d'être particulièrement précautionneux quant aux opérations effectuées dans de telles zones temporairement privilégiées (attention à ce que l'on fait, ce que l'on permet de faire) et d'une manière générale d'y effectuer le minimum possible d'opérations.

Cliché du contexte

(AccessControlContext / thread) pour examen d'une permission dans un autre contexte : rapport avec le GuardedObject ?

Domaine de Protection

Un domaine de protection représente un modèle similaire à celui de la sanbox de Java 1.1, où l'identification par CODEBASE serait étendue au concept d'origine de code.

Un domaine de protection associe un ensemble de permissions à une origine de code donnée.

Il existe typiquement deux catégories de domaines de protection :

  • le domaine "système", capable d'accéder aux ressources de la machine
  • le domaine "applicatif", nécessitant parfois de recourir momentanément au fonctionnalités offertes au domaine système (de la même manière que sous Unix on passe momentanément "root" pour effectuer certaines opérations utilisateur) au travers d'une exécution privilégiée.

Contexte de Sécurité

L'exécution du code de plusieurs classes par un même thread implique donc la traversée de plusieurs domaines de protection. On définit le Contexte de Sécurité comme l'intersection des permissions accordées par les Domaines de Protection traversés.

Une classe autorisée à écrire des fichiers pourra par exemple appeler une classe non autorisée à écrire de tels fichiers. Dans ce cas, la règle de l'intersection des domaines de protection ne permettra pas au code de la classe non privilégiée de disposer des privilèges de la classe appelante (l'intersection correspond à la permission la plus faible des deux).

Il est possible à tout moment de demander au Contrôleur d'Accès quel est le Contexte de Sécurité courant (du thread courant), via la méthode statique AccessControler.getContext(). Cette méthode retourne un objet de type java.security.AccessControlContext ou l'un de ces descendants (il pourra en effet être intéressant de spécialiser le contexte de sécurité pour y inclure de nouveaux paramètres).

Blocs et actions privilégiés

Il peut arriver qu'une classe privilégiée souhaite transmettre temporairement des privilèges à une classe moins privilégiée. Cette opération s'effectue au travers de l'exécution d'un "bloc privilégié".

L'exécution d'un Bloc Privilégié consiste à demander l'exécution d'une Action Privilégiée. Une Action Privilégiée est une classe contenant le code devant disposer de privilèges. Une telle classe doit implémenter l'interface java.security.PrivilegedAction, qui impose de fournir le code d'une méthode run(), où doit se trouver le code privilégié.

Par exemple :

class PrivilegedUserNameAccess implements <strong>java.security.PrivilegedAction</strong> {<br> public Object run() {<br> return System.getProperty (<span class="codeString">"user.name"</span>); <br> }<br> }

L'exécution de cette action privilégiée est alors demandée au Contrôleur d'Accès :

String userName = (String) <strong>AccessControler.doPrivileged</strong> (new PrivilegedUserNameAccess());

Dans cet exemple, le bloc permet de retourner la valeur d'une propriété qui ne serait pas accessible si la classe appelante ne disposais pas de la permission nécessaire (en l'occurence java.util.PropertyPermission ("user.name", "read")).

Cependant, il est possible de faire bénéficier une action privilégiée d'un contexte de sécurité différent de celui de la classe appelante (un contexte plus limité par exemple). Dans ce cas, il suffit de fournir à la méthode doPrivileged() le contexte adéquat :

String userName = (String) <strong>AccessControler.doPrivileged</strong> (new PrivilegedUserNameAccess(), someActionContext);