Java Cryptography Architecture : architecture de cryptographie de Java (JCA est aussi souvent utilisé pour désigner les connecteurs J2EE).
Besoin
Fonctionnalités cryptographiques pour les applications Java.
Analyse
Dans un esprit d'abstraction des couches propriétaires, la JCA distingue :
- les concepts de sécurité (identité, clés, certificats...)
- généraux
- Condensés de messages
- Génération de clés asymétriques (privées et publiques)
- Signatures électroniques
- Paramètres d'algorithmes
- spécifiques à Java
- généraux
- les implémentations de ces concepts (RSA, DSA, SHA, MD5)
Il est ainsi possible d'utiliser les implémentations cryptographiques de différents fournisseurs tout en garantissant une interopérabilité entre ces différentes implémentations par le biais d'interfaces standardisées (les concepts).
Implémentation
La JCA est constituée des packages java.security.*
.
Implémentation des concepts généraux
Identité
Les identités sont représentées par des classes implémentant java.security.Principal
. depuis Java 2
(dans Java 1, la classe java.security.Identity
implémentait cette interface et fournissait diverses
méthodes permettant d'associer des clés et certificats à cette identité. Java 2 a rendu
cette dernière classe obsolète avec l'apparition de la base de clés java.security.Keystore
,
qui permet d'associer clés et certificats à partir d'un alias unique -- alias
qu'un objet de type java.security.Principal
peut fournir via sa méthode
getName()
. La classe java.security.Identity
est donc obsolète à partir de Java 2, ainsi
que ses dérivées java.security.IdentityScope
et java.security.Signer
.)
Clés
Le concept de clé est représenté par l'interface java.security.Key
, dont dérivent
les interfaces java.security.PublicKey
(clé publique) et java.security.PrivateKey
(clé privée).
Clé publique et clé privée peuvent être associées dans un objet java.security.KeyPair
. Ces paires de
clés peuvent être générées à l'aide d'un générateur de paires de clés spécifique à un algorithme. Par exemple :
import java.security.*;
KeyPairGenerator myKeyPairGenerator = KeyPairGenerator.getInstance("DSA");
Toute génération de paire de clé s'effectue sur la base de deux critères :
- la taille de la clé (un entier représentant le nombre de bits sur lesquels coder la clé, compatible avec l'algorithme du générateur de clés).
- une source aléatoire, permettant de garantir l'unicité de la clé. Cette source est en fait pseudo-aléatoire (PRNG) et représentée par la classe
java.security.SecureRandom
, dérivable par divers fournisseurs.
Par exemple :
SecureRandom myPRNG = SecureRandom.getInstance("SHA1PRNG");
myKeyPairGenerator.initialize(1024, myPRNG);
La génération s'effectue enfin via la méthode genKeyPair(). Attention, cette opération peut prendre plusieurs secondes (notamment en fonction de la taille de la clé) :
KeyPair myKeyPair = myKeyPairGenerator.genKeyPair();
Ces clés sont opaques. L'accès transparent aux données d'une clé s'effectue via l'interface java.security.spec.KeySpec.
Condensé de message
Un condensé de message est initialisé à partir d'une fabrique, en fonction de son algorithme (SHA, MD5). Par exemple :
MessageDigest monCondense = MessageDigest.getInstance("SHA");
Reste ensuite à fournir les données à soumettre à la fonction de hachâge, sous forme de tableaux d'octets :
byte[] donnees1= { 1, 21, 3, 4 };
byte[] donnees2 = { 5, 2, 3, 7, 8, 12};
monCondense.update(donnees1);
monCondense.update(donnees2);
La génération des données du condensé se faisant via sa méthode digest()
.
byte[] donneesCondense = monCondense.digest();
Il est également possible de générer des condensés à partir de données d'autres condensés. Il suffit pour cela de cloner les condensés afin de ne pas agir sur les mêmes données mais sur des copies de ces données.
Par exemple :
monCondense.update(donnees1);
byte[] donneesCondense1 = monCondense.clone().digest();
monCondense.update (donnees2);
byte[] donneesCondense1et2 = monCondense.digest();
Signature
ACL/Permissions
Implémentation des concepts spécifiques à Java
Les concepts suivants ont été spécifiquement définis pour s'intégrer dans la plate-forme Java 2.
Objets gardés
Les objets gardés sont représentés par la classe java.security.GuardedObject
.
Dans Java 2, tous les objets Permission
sont des gardes, ce qui permet de retourner facilement un
objet dont l'accès est soumis à une permission donnée (un ensemble de permissions ?) au travers d'un GuardedObject.
Politique de Sécurité
Java 2 permet de définir une politique de sécurité. Cette nouvelle possibilité offre les avantages suivants :
- La définition d'une politique de sécurité offre une expression claire de ce qui est autorisé et interdit à l'acteur définissant cette politique, et éventuellement aux utilisateurs soumis à cette politique.
- Les permissions peuvent être définies de manière plus fine que dans Java 1 (qui n'offrait qu'une permission "tout ou rien")
- les politiques de sécurités peuvent être définies à plusieurs niveaux : pour la plate-forme et pour chaque utilisateur. Il est donc possible de définir des Politiques de Sécurité différentes pour les utilisateurs d'une même machine (on pourra par exemple définir des politiques différentes pour l'Administrateur de la machine, pour un compte destiné à tester les logiciels téléchargés potentiellement dangereux, etc.).
Concrètement, ces politiques sont exprimées sous la forme de séquences de permissions (comparables au principe des ACL).
Origine de code
Une origine de code (Code Source) représente une identification de l'émetteur d'un code applicatif.
Cette identification peut s'effectuer sur la base de :
- l'URL de provenance du code
- le(s) certificat(s) du code s'il est signé
- les deux
Il s'agit donc d'une extension du concept de CODEBASE, utilisé dans Java 1.1 pour associer du code et un ClassLoader.
Lors de l'examen de l'origine d'un code, on procède donc à une comparaison de l'origine du code (télé)chargé et de l'origine de code déclarée comme valide, qui peut comporter des jokers.
On peut par exemple décréter qu'une origine de code acceptable (constituée uniquement d'une URL dans ce cas) est :
http://*.valtech.fr/classes/*
Ainsi que le code téléchargé à l'aide de l'URL
http://javarome.valtech.fr/classes/security/Example.class
sera considéré comme valide par exemple.
Cette possibilité peut être utilisée au travers de java.security.CodeSource.implies (CodeSource),
qui
renvoie si l'origine d'un code (URL et certficats) est compatible avec l'origine d'un autre code.
Chargeur de classe
Vérifieur de bytecode
Gestionnaire de Sécurité
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 java.security.PrivilegedAction
{
public
Object run()
{
return System.getProperty ("user.name");
}
}
L'exécution de cette action privilégiée est alors demandée au Contrôleur d'Accès :
String userName = (String) AccessControler.doPrivileged (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) AccessControler.doPrivileged (new PrivilegedUserNameAccess(),
someActionContext);
Base de clés
Une base de clés (Key Store) constitue un référentiel de clés et certificats.
Une entrée de la base de clés peut être :
- une clé secrète
- une chaînes de certificats associée à une clé privée
- un certificat associé à une clé publique
Cette base est représentée par la classe java.security.KeyStore
et son mode de persistance peut être
fourni par divers fournisseurs implémentant une java.security.KeyStoreSpi
(sur une carte à puce par
exemple).
Objet Signé
Un objet signé est un objet dont l'état courant a été signé à l'aide d'un certificat.
Exemples
Notes
- les signatures électroniques [1.1]
- les condensés de messages [1.1]
- Génération de clés asymétriques (privées et publiques)
- Paramètres d'algorithmes
- le support des certficats X.509 V3.
- peut être étendue par la JCE.