Les greffons Java
Appeler un programme en Java à partir d'un navigateur et lui permettre d'accéder au système local demande la mise en œuvre de tout un jeu de techniques. Apprenez à écrire une application graphique qui peut s'exécuter comme une applette puis à la signer et à l'interfacer en HTML et en Javascript.
Créez un dossier appelé java puis un dossier jdigest dans le dossier java.
- java
- jdigest
Dans Eclipse, créez un projet Java appelé Jdigest dans le dossier java/jdigest. Si vous n'utilisez pas Eclipse, créez l'arborescence suivante :
- java/jdigest
- bin
- src
- org
- frasq
- jdigest
- frasq
- org
Commande
Créez la classe JDigestCommand
dans le paquet org.frasq.jdigest
en ajoutant le fichier JDigestCommand.java dans le dossier src/org/frasq/jdigest avec le contenu suivant :
- src/org/frasq/jdigest
- JDigestCommand.java
- package org.frasq.jdigest;
- import java.io.InputStream;
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.security.MessageDigest;
- import java.security.DigestInputStream;
- import java.security.NoSuchAlgorithmException;
Définit le paquet du programme. Importe les classes nécessaires à la lecture d'un fichier et à la génération d'une empreinte numérique.
- public class JDigestCommand {
- private static final String APPLICATIONNAME = "JDigest";
- private static final String APPLICATIONVERSION = "1.1";
- private static final String APPLICATIONREVISION = "2";
- private static MessageDigest md;
- public static boolean traced = false;
Définit le nom et les numéros de version et de révision du programme.
traced
à true
affiche les messages d'erreurs et les messages de trace du programme.
NOTE : La valeur de traced
peut être définie directement à true
dans le code pendant le développement ou à partir de la ligne de commande, d'un fichier de configuration ou d'un paramètre d'appel d'une applette.
md
contient l'objet MessageDigest
utilisé par le programme pour générer une empreinte numérique.
- public static void trace(final Exception e) {
- if (traced) {
- try {
- StackTraceElement s = Thread.currentThread().getStackTrace()[3];
- String c = Class.forName(s.getClassName()).getSimpleName();
- String m = s.getMethodName();
- System.err.println(c + ":" + m + ": " + e);
- }
- catch (Exception none) {
- }
- }
- }
- public static void trace(final String s) {
- if (traced)
- System.err.println(s);
- }
Les méthodes trace
affichent la description d'une exception ou une simple chaîne de caractères sur le flot de sortie des erreurs si la variable traced
est à true
.
- public static String name() {
- return APPLICATIONNAME;
- }
- public static String version() {
- return APPLICATIONVERSION;
- }
- public static String revision() {
- return APPLICATIONREVISION;
- }
- public static String signature() {
- return APPLICATIONNAME + ' ' + version() + " (" + revision() + ")";
- }
Définit les méthodes qui retournent le nom, les numéros de version et de révision du programme ainsi que sa signature complète.
- public static void usage() {
- System.out.println("jdigest [-trace] file ...");
- System.out.println("jdigest -version");
- }
La méthode usage
affiche un rappel des différentes options d'exécution du programme.
- public static void main(String[] args) {
- int i = 0;
- String arg;
- while (i < args.length && args[i].startsWith("-")) {
- arg = args[i++];
- if (arg.equals("-trace"))
- traced = true;
- else if (arg.equals("-version")) {
- System.out.println(signature());
- System.exit(0);
- }
- else {
- usage();
- System.exit(1);
- }
- }
- if (i >= args.length){
- usage();
- System.exit(1);
- }
Le programme commence par analyser la ligne de commande pour en extraire les options.
-trace
met la variable traced
à true
.
-version
affiche la signature du programme et sort.
Toute autre option non reconnue affiche le rappel des différentes options d'exécution du programme et sort.
Si le programme n'est pas exécuté avec au moins un nom de fichier en argument, il affiche son usage et sort.
- while (i < args.length) {
- String fname = args[i++];
- byte[] sha1 = digestFile(fname);
- if (sha1 != null) {
- System.out.println(bytesToHexString(sha1) + " " + fname);
- }
- }
- }
Parcourt la liste des paramètres suivant les options, passe chaque paramètre à la méthode digestFile
et affiche l'empreinte générée en hexadécimal à l'aide de la méthode bytesToHexString
.
- private static byte[] digestFile(String filename) {
- if (md == null) {
- try {
- md = MessageDigest.getInstance("SHA1");
- }
- catch (NoSuchAlgorithmException e) {
- trace(e);
- }
- }
- if (md != null) {
- try {
- InputStream is = new FileInputStream(filename);
- DigestInputStream gis = new DigestInputStream(is, md);
- byte[] buf = new byte[4096];
- while (gis.read(buf) != -1)
- ;
- return md.digest();
- }
- catch (IOException e) {
- trace(e);
- }
- }
- return null;
- }
digestFile
retourne le hachage SHA1 du fichier filename
dans un byte[]
.
En cas d'erreur, digestFile
trace les exceptions et retourne null
.
Le code crée un MessageDigest
du type SHA1 à la demande et le sauvegarde dans la variable statique md
,
crée un InputStream
appelé is
ouvert sur le fichier filename
,
crée un objet DigestInputStream
appelé gis
avec les paramètres is
et md
,
lit le fichier et retourne son empreinte.
- private static char hexdigit[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
- private static String bytesToHexString(byte[] b) {
- StringBuffer sb = new StringBuffer();
- for (int i = 0; i < b.length; i++) {
- sb.append(hexdigit[(b[i] >> 4) & 0x0f]);
- sb.append(hexdigit[b[i] & 0x0f]);
- }
- return sb.toString();
- }
- }
bytesToHexString
retourne une chaîne de caractères contenant la représentation en hexadécimal de b
.
Compilez le programme en plaçant le binaire dans le dossier bin :
$ javac -d bin src/org/frasq/jdigest/JDigestCommand.java
Exécutez le programme :
$ java -cp bin org.frasq.jdigest.JDigestCommand
jdigest [-trace] file ...
jdigest -version
$ java -cp bin org.frasq.jdigest.JDigestCommand -version
JDigest 1.1 (2)
$ java -cp bin org.frasq.jdigest.JDigestCommand -trace bin/org/frasq/jdigest/JDigestCommand.class
f5bdd117deab31fb99f91b44e3b6744036db7f8f bin/org/frasq/jdigest/JDigestCommand.class
Comparez le résultat avec la sortie de la commande sha1sum
:
$ sha1sum bin/org/frasq/jdigest/JDigestCommand.class
f5bdd117deab31fb99f91b44e3b6744036db7f8f bin/org/frasq/jdigest/JDigestCommand.class
Application
Créez le dossier src/org/frasq/jdigest/images à la racine du projet puis copiez les fichiers clock_16x16.png et open_16x16.png dans le dossier images :
- src/org/frasq/jdigest
- images
- clock_16x16.png
- open_16x16.png
- images
Ajoutez les fichiers parameters.properties et strings.properties dans le dossier src/org/frasq/jdigest avec les contenus suivants :
- src/org/frasq/jdigest
- parameters.properties
- strings.properties
- useWindowsLAF=1
- useNimbusLAF=0
- replaceGtkWithNimbus=0
- replaceGtkFileChooser=0
useWindowsLAF
à 1
affiche l'application avec l'habillage Windows, s'il est disponible.
useNimbusLAF
à 1
affiche l'application avec l'habillage Nimbus, normalement disponible sur tous les systèmes.
replaceGtkWithNimbus
à 1
affiche l'application avec l'habillage Nimbus si l'habillage par défaut est GTK.
replaceGtkFileChooser
à 1
remplace le FileChooser
standard.
NOTE : Pour employer l'option replaceGtkFileChooser
, téléchargez l'archive gtkjfilechooser.jar, copiez-la dans le dossier jre/lib/ext du répertoire d'installation du JDK.
- applicationFrameTitle=Jdigest
- applicationFrameIcon=images/clock_16x16.png
- openIcon=images/open_16x16.png
- openToolTip=Open a file
Ajoutez une version en français :
- openToolTip=Ouvrez un fichier
applicationFrameTitle
définit le titre de l'application affiché dans le bandeau de la fenêtre.
applicationFrameIcon
donne le nom du fichier contenant l'icône de l'application.
openIcon
définit l'icône du bouton qui ouvre le dialogue de sélection d'un fichier.
IMPORTANT : Enregistrez les fichiers en ISO-8859-1.
Pensez à copier le dossier images et les fichiers parameters.properties et strings.properties dans le dossier bin/org/frasq/jdigest. NOTE : Eclipse copiera automatiquement les fichiers avant de lancer l'exécution du programme.
Dans Eclipse, copiez la classe JDigestCommand
et nommez-la JDigestApp
.
Si vous n'utilisez pas Eclipse, éditez le fichier JDigestCommand.java, remplacez toutes les références à la classe JDigestCommand
par JDigestApp
et sauvez le fichier en le nommant JDigestApp.java dans le dossier src/org/frasq/jdigest.
- src/org/frasq/jdigest
- JDigestCommand.java
- JDigestApp.java
Éditez le fichier JDigestApp.java :
- package org.frasq.jdigest;
- import java.io.File;
- import java.io.InputStream;
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.security.MessageDigest;
- import java.security.DigestInputStream;
- import java.security.NoSuchAlgorithmException;
- import javax.swing.*;
- import javax.swing.UIManager.LookAndFeelInfo;
- import java.awt.*;
- import java.awt.event.*;
- import java.awt.datatransfer.*;
Définit le paquet du programme. Importe les classes nécessaires à la lecture d'un fichier, à la génération d'une empreinte numérique et à l'affichage de l'interface graphique.
- public class JDigestApp implements ActionListener {
- private static final java.util.ResourceBundle PARAMETERS = java.util.ResourceBundle.getBundle(JDigestApp.class.getPackage().getName()
- + "." + "parameters");
- private static final java.util.ResourceBundle STRINGS = java.util.ResourceBundle.getBundle(JDigestApp.class.getPackage().getName()
- + "." + "strings");
Définit la classe JDigestApp
qui implémente l'interface ActionListener
.
Charge les ressources définies dans les fichiers org/frasq/jdigest/parameters.properties et org/frasq/jdigest/strings.properties.
- private static final String APPLICATIONFRAMETITLE = "applicationFrameTitle";
- private static final String APPLICATIONFRAMEICON = "applicationFrameIcon";
- private static final String USEWINDOWSLAF = "useWindowsLAF";
- private static final String USENIMBUSLAF = "useNimbusLAF";
- private static final String REPLACEGTKFILECHOOSER = "replaceGtkFileChooser";
- private static final String REPLACEGTKWITHNIMBUS = "replaceGtkWithNimbus";
- private static final String OPEN = "open";
- private static String applicationTitle;
- private JFrame frame;
- private JPanel panel;
- private JButton open;
- private JTextField sha1sum;
- private JFileChooser chooser;
Définit les noms des ressources. Définit les variables qui contiennent les différents composants de l'interface graphique.
usage
affiche une ligne de plus :
- public static void usage() {
- System.out.println("jdigest [-trace]");
- System.out.println("jdigest [-trace] file ...");
- System.out.println("jdigest -version");
- }
Le code de la méthode main
est pratiquement inchangé sauf pour appeler la méthode createAndShowGUI
si aucun nom de fichier n'a été passé en argument sur la ligne de commande :
- if (i < args.length) {
- while (i < args.length) {
- String fname = args[i++];
- byte[] sha1 = digestFile(fname);
- if (sha1 != null) {
- System.out.println(bytesToHexString(sha1) + " " + fname);
- }
- }
- System.exit(0);
- }
- javax.swing.SwingUtilities.invokeLater(new Runnable() {
- @Override
- public void run() {
- createAndShowGUI();
- }
- });
Remarquez comment la méthode createAndShowGUI
est appelée dans un fil d'exécution séparé.
- private static void createAndShowGUI() {
- applicationTitle = getString(APPLICATIONFRAMETITLE);
- JDigestApp app = new JDigestApp();
- initLookAndFeel();
- JFrame.setDefaultLookAndFeelDecorated(false);
- JDialog.setDefaultLookAndFeelDecorated(false);
- JFrame frame = app.createFrame();
- frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- frame.setLocationRelativeTo(null);
- frame.setVisible(true);
- }
createAndShowGUI
initialise le titre de l'application avec getString
et le paramètre APPLICATIONFRAMETITLE
, crée l'instance du programme, règle le Look & Feel de l'interface avec initLookAndFeel
, opte pour une décoration des fenêtres intégrée au système, crée le cadre principal de l'application avec createFrame
et l'affiche au centre de l'écran.
- public static String getString(String key) {
- try {
- return STRINGS.getString(key);
- }
- catch (java.util.MissingResourceException e) {
- }
- return null;
- }
getString
retourne la chaîne de caractères associée à key
dans le ResourceBundle
STRINGS
.
Si key
n'est pas défini dans STRINGS
, getString
retourne null
, sans tracer d'erreur puisque qu'une ressource peut être optionnelle.
- private static void initLookAndFeel() {
- String lafWindows = null, lafNimbus = null;
- UIManager.put("FileChooser.readOnly", Boolean.TRUE);
- for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
- if (info.getName() == "Nimbus")
- lafNimbus = info.getClassName();
- else if (info.getName() == "Windows")
- lafWindows = info.getClassName();
- }
- try {
- if (lafWindows != null && getBooleanParameter(USEWINDOWSLAF, false))
- UIManager.setLookAndFeel(lafWindows);
- else if (lafNimbus != null && getBooleanParameter(USENIMBUSLAF, false))
- UIManager.setLookAndFeel(lafNimbus);
- else if (UIManager.getLookAndFeel().getID() == "GTK") {
- if (lafNimbus != null && getBooleanParameter(REPLACEGTKWITHNIMBUS, false))
- UIManager.setLookAndFeel(lafNimbus);
- else if (getBooleanParameter(REPLACEGTKFILECHOOSER, false))
- UIManager.put("FileChooserUI", "eu.kostia.gtkjfilechooser.ui.GtkFileChooserUI");
- }
- }
- catch (Exception e) {
- trace(e);
- }
- }
initLookAndFeel
commence par vérifier si les Look & Feel Windows et Nimbus sont disponibles puis, selon les valeurs des paramètres de configuration USEWINDOWSLAF
, USENIMBUSLAF
, REPLACEGTKWITHNIMBUS
et REPLACEGTKFILECHOOSER
, change l'habillage de l'application ou, dans le dernier cas, le widget qui affiche le dialogue de sélection d'un fichier.
- private static String getParameter(String key, String fallback) {
- try {
- return PARAMETERS.getString(key);
- }
- catch (java.util.MissingResourceException e) {
- trace(key + "?");
- }
- return fallback;
- }
- private static boolean getBooleanParameter(String key, boolean fallback) {
- try {
- String p = getParameter(key, null);
- if (p != null)
- return Integer.parseInt(p) == 0 ? false : true;
- }
- catch (Exception e) {
- trace(key + "?");
- }
- return fallback;
- }
getParameter
retourne la chaîne de caractères associée à key
dans le ResourceBundle
PARAMETERS
.
Si key
n'est pas défini dans PARAMETERS
, getParameter
trace l'exception et retourne fallback
.
getBooleanParameter
retourne false
si le paramètre de configuration key
représente l'entier 0 ou true
pour tout autre entier.
Si key
n'est pas défini dans PARAMETERS
ou si la valeur du paramètre ne représente pas un entier, getBooleanParameter
retourne fallback
.
- public JFrame createFrame() {
- frame = new JFrame(applicationTitle);
- frame.setIconImage(createImage(getString(APPLICATIONFRAMEICON)));
- frame.setBackground(Color.WHITE);
- panel = createPanel();
- frame.setContentPane(panel);
- frame.pack();
- frame.setResizable(false);
- return frame;
- }
createFrame
crée un cadre avec le titre applicationTitle
, génère l'image définie par le paramètre APPLICATIONFRAMEICON
avec createImage
et la définit comme son icône puis crée son contenu avec createPanel
.
- public static Image createImage(String path) {
- java.net.URL url = JDigestApp.class.getResource(path);
- if (url == null) {
- trace(path + "?");
- return null;
- }
- return Toolkit.getDefaultToolkit().getImage(url);
- }
createImage
fabrique une URL à partir de path
et retourne l'image correspondante générée par le Toolkit
.
En cas d'erreur, createImage
trace path
et retourne null
.
- public JPanel createPanel() {
- panel = new JPanel();
- panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
- panel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
- open = createButton(OPEN, this);
- open.setPreferredSize(open.getMinimumSize());
- panel.add(open, BorderLayout.LINE_START);
- sha1sum = new JTextField(40);
- sha1sum.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5));
- sha1sum.setFont(new Font("Monospaced", Font.BOLD, 12));
- sha1sum.setHorizontalAlignment(JTextField.CENTER);
- sha1sum.setEditable(false);
- sha1sum.setText("0000000000000000000000000000000000000000");
- panel.add(sha1sum, BorderLayout.LINE_END);
- return panel;
- }
createPanel
retourne un panneau qui aligne un bouton et un champ textuel.
Le code crée un JPanel
associé à un BoxLayout
en mode LINE_AXIS, crée un JButton
avec createButton
placé en début de ligne puis un JTextField
placé en fin de ligne dont le contenu est centré et non éditable.
- public static JButton createButton(String item, ActionListener listener) {
- return (JButton) createAbstractButton(JButton.class, item, listener);
- }
createButton
retourne un JButton
fabriqué par createAbstractButton
avec les paramètres item
et listener
.
- private static AbstractButton createAbstractButton(Class<? extends AbstractButton> c, String item,
- ActionListener listener) {
- AbstractButton button = (AbstractButton) createObject(c);
- String label = getString(item + "Label");
- if (label != null)
- button.setText(label);
- String href = getString(item + "Icon");
- ImageIcon icon = href != null ? createIcon(href) : null;
- if (icon != null)
- button.setIcon(icon);
- if (label == null && icon == null)
- button.setText(item);
- String tip = getString(item + "ToolTip");
- if (tip != null)
- button.setToolTipText(tip);
- button.setFocusPainted(false);
- button.setActionCommand(item);
- button.addActionListener(listener);
- return button;
- }
createAbstractButton
retourne un AbstractButton
du type c
et l'associe à l'ActionListener
listener
.
item
sert à lire les paramètres de configuration dont les noms commencent par la valeur de item
et se terminent par Label pour le label du bouton, Icon pour l'image de son icône et ToolTip pour le texte de la bulle d'aide.
Un bouton peut avoir une icône et un label.
Un bouton sans label et sans icône a la valeur de item
pour label.
- public static Object createObject(Class<?> c) {
- Object object = null;
- try {
- object = c.newInstance();
- }
- catch (InstantiationException e) {
- trace(e);
- }
- catch (IllegalAccessException e) {
- trace(e);
- }
- return object;
- }
- public static Object createObject(String className) {
- Object object = null;
- try {
- Class<?> classDefinition = Class.forName(className);
- object = createObject(classDefinition);
- }
- catch (ClassNotFoundException e) {
- trace(e);
- }
- return object;
- }
Les méthodes createObject
retournent un objet de la classe c
ou dont la classe a pour nom className
.
- public void actionPerformed(ActionEvent e) {
- String cmd = e.getActionCommand();
- if (cmd == OPEN)
- openFile();
- }
Activer le bouton OPEN
appelle la méthode actionPerformed
qui appelle la méthode openFile
.
- public void openFile() {
- if (chooser == null) {
- chooser = new JFileChooser();
- chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
- chooser.setControlButtonsAreShown(true);
- chooser.setFileHidingEnabled(false);
- chooser.setMultiSelectionEnabled(false);
- chooser.setAcceptAllFileFilterUsed(true);
- }
- int r = chooser.showOpenDialog(sha1sum);
- if (r == JFileChooser.APPROVE_OPTION) {
- File file = chooser.getSelectedFile();
- if (file.exists()) {
- byte[] sha1 = digestFile(file.getPath());
- if (sha1 != null) {
- String s = bytesToHexString(sha1);
- trace(s + " " + file.getName());
- sha1sum.setText(s);
- Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
- Transferable transferable = new StringSelection(s);
- clipboard.setContents(transferable, null);
- }
- }
- }
- }
openFile
crée un JFileChooser
à la demande et le sauvegarde dans la variable chooser
.
Le dialogue est centré sur le champ de texte.
Si le retour du dialogue est positif, openFile
en extrait le nom de fichier, vérifie que le fichier existe et appelle digestFile
.
Si digestFile
ne renvoie pas null
, openFile
convertit le byte[]
retourné en hexadécimal, trace l'empreinte, l'affiche dans le champ de texte et la copie dans le presse-papier.
Compilez le programme puis exécutez-le :
$ javac -d bin src/org/frasq/jdigest/JDigestApp.java
$ java -cp bin org.frasq.jdigest.JDigestApp -trace
Sous Linux avec le Look & Feel GTK de Gnome :
Sous Windows avec le paramètre useWindowsLAF
à 1
:
Essayez avec le paramètre useNimbusLAF
à 1
:
Cliquez sur le bouton et sélectionnez un fichier.
Le Look & Feel du dialogue varie beaucoup selon les versions.
Celle pour le GTK est vraiment minable.
Essayez avec le paramètre replaceGtkWithNimbus
à 1
puis à 0
avec le paramètre replaceGtkFileChooser
à 1
.
NOTE : Pensez à copier le fichier parameters.properties dans le dossier bin/org/frasq/jdigest à chaque fois que vous modifiez le fichier source.
Remarquez que la commande marche toujours :
$ java -cp bin org.frasq.jdigest.JDigestApp bin/org/frasq/jdigest/JDigestApp.class
6ad8ff8b0429d6c28e7518cebb5c2623ece7343b bin/org/frasq/jdigest/JDigestApp.class
Applette
Pour transformer l'application en une applette, copiez dans Eclipse la classe JDigestApp
en la renommant JDigest
.
Si vous n'utilisez pas Eclipse, éditez le fichier JDigestApp.java, remplacez toutes les références à la classe JDigestApp
par JDigest
et sauvez le fichier en le nommant JDigest.java dans le dossier src/org/frasq/jdigest :
- src/org/frasq/jdigest
- JDigestCommand.java
- JDigestApp.java
- JDigest.java
Éditez le fichier JDigest.java :
- public class JDigest extends JApplet implements ActionListener {
La classe JDigest
hérite de la classe JApplet
.
Ajoutez la méthode init
:
- public void init() {
- String p;
- try {
- p = getParameter("traced");
- if (p != null)
- traced = Integer.parseInt(p) == 0 ? false : true;
- }
- catch (java.lang.NumberFormatException e) {
- }
- initLookAndFeel();
- panel = createPanel();
- panel.setOpaque(true);
- setContentPane(panel);
- }
init
lit le paramètre traced
passé par le navigateur, initialise le Look & Feel de l'interface graphique, crée le panneau de l'application et le définit comme le contenu de l'applette.
Compilez le programme et fabriquez un jar avec tous les fichiers nécessaires à l'applette :
$ rm -fr bin
$ mkdir -p bin/org/frasq/jdigest
$ cp -r src/org/frasq/jdigest/images bin/org/frasq/jdigest
$ cp src/org/frasq/jdigest/*.properties bin/org/frasq/jdigest
$ javac -d bin src/org/frasq/jdigest/JDigest.java
$ ls bin/org/frasq/jdigest
images JDigest$1.class JDigest.class parameters.properties strings_fr.properties strings.properties
$ ls bin/org/frasq/jdigest/images
clock_16x16.png open_16x16.png
Créez le jar jdigest.jar :
$ jar -cvf jdigest.jar -C bin org/frasq/jdigest
Remarquez que l'application et la commande marchent toujours :
$ java -jar jdigest.jar jdigest.jar
a2d1c18100345011d808e7327465d59c5fc38363 jdigest.jar
$ java -jar jdigest.jar -trace
Ajoutez le fichier jdigestfail.html à la racine du projet avec le contenu suivant :
- java/jdigest
- jdigestfail.html
- jdigest.jar
- <html>
- <head>
- <title>JDigest</title>
- </head>
- <body>
- <applet id="jdigest" code="org.frasq.jdigest.JDigest" archive="jdigest.jar" width="350" height="30"><param name="traced" value="1"/></applet>
- </body>
- </html>
Essayez l'applette :
$ appletviewer jdigestfail.html
Placez le pointeur de la souris sur le bouton pour afficher la bulle d'aide. Cliquez. Une erreur est déclenchée par le gestionnaire de la sécurité.
java.security.AccessControlException: access denied (java.util.PropertyPermission user.home read)
Le programme n'a pas l'autorisation d'ouvrir un fichier local. Vous devez signer le jar.
Signature
Installez OpenSSL :
$ sudo apt-get install openssl
Créez un dossier appelé ssl :
$ mkdir ssl
Allez dans le dossier ssl :
$ cd ssl
Créez les dossiers private, certs et newcerts et protégez-les :
$ mkdir private certs newcerts
$ chmod 700 private certs newcerts
Créez l'index et initialisez le numéro de série des certificats :
$ touch index.txt
$ echo 01 > serial
Copiez le fichier de configuration /etc/ssl/openssl.cnf :
$ cp /etc/ssl/openssl.cnf .
Éditez le fichier openssl.cnf et changez les lignes suivantes :
dir = . # Where everything is kept
countryName = optional
stateOrProvinceName = optional
[ req_distinguished_name ]
#countryName = Country Name (2 letter code)
#stateOrProvinceName = State or Province Name (full name)
#localityName = Locality Name (eg, city)
0.organizationName_default = Frasq
[ usr_cert ]
nsCertType = objsign
#nsComment = "OpenSSL Generated Certificate"
nsComment = "Generated by Frasq"
IMPORTANT : L'option nsCertType
de la section [ usr_cert ]
dépend de l'extension voulue.
server
génère le certificat d'un serveur.
client, email
génère le certificat d'un utilisateur contenant une adresse d'email.
objsign
génère un certificat utilisable pour signer des fichiers.
Commencez par créer le certificat de votre autorité de certification :
$ openssl req -config ./openssl.cnf -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 3650
Les options -x509
et -extensions v3_ca
créent directement un certificat racine auto-signé.
Entrez changeit
comme mot de passe de la clé.
Donnez les réponses suivantes aux questions :
Organization Name (eg, company) [Frasq]:
Organizational Unit Name (eg, section) []:http://www.frasq.org
Common Name (eg, YOUR name) []:Frasq Signing Authority
Email Address []:keymaster@frasq.org
Vérifiez le contenu du fichier :
$ openssl x509 -in cacert.pem -text -noout
...
Signature Algorithm: sha1WithRSAEncryption
Issuer: O=Frasq, OU=http://www.frasq.org, CN=Frasq Signing Authority/emailAddress=keymaster@frasq.org
...
Subject: O=Frasq, OU=http://www.frasq.org, CN=Frasq Signing Authority/emailAddress=keymaster@frasq.org
...
X509v3 Basic Constraints:
CA:TRUE
...
Notez que les champs Issuer
et Subject
sont identiques et que le champ X509v3 Basic Constraints
est à CA:TRUE
.
Créez une demande de certificat :
$ openssl req -config ./openssl.cnf -new -out signer-req.pem -keyout private/signer-key.pem
Entrez changeit
comme mot de passe de la clé.
Donnez les réponses suivantes aux questions :
Organization Name (eg, company) [Frasq]:
Organizational Unit Name (eg, section) []:http://www.frasq.org
Common Name (eg, YOUR name) []:Frasq Signing Authority
Email Address []:keymaster@frasq.org
Signez la demande de certificat :
$ openssl ca -config ./openssl.cnf -out newcerts/signer-cert.pem -infiles signer-req.pem
Créez un certificat au format PKCS12 :
$ openssl pkcs12 -export -out signer.p12 -in newcerts/signer-cert.pem -inkey private/signer-key.pem
-name "Signer" -caname "Frasq Signing Authority"
Revenez dans le dossier du projet :
$ cd ..
Créez un trousseau Java appelé frasq.jks contenant le certificat de l'autorité de certification :
$ keytool -keystore frasq.jks -storepass changeit -importcert -alias ca -file ssl/cacert.pem
Ajoutez le certificat et la clé du signataire :
$ keytool -importkeystore -srckeystore ssl/signer.p12 -destkeystore frasq.jks -srcstoretype pkcs12
Vérifiez le contenu du trousseau :
$ keytool -keystore frasq.jks -storepass changeit -list
Signez le jar :
$ jarsigner -keystore frasq.jks -storepass changeit -signedjar sjdigest.jar jdigest.jar signer
Vérifiez le jar signé :
$ jarsigner -keystore frasq.jks -storepass changeit -verify -verbose -certs sjdigest.jar
Lisez l'article Les outils du développeur web pour des explications détaillées sur les certificats et le chiffrement des communications.
Renommez le fichier jdigestfail.html jdigest.html :
- java/jdigest
- jdigest.html
- sjdigest.jar
Remplacez jdigest.jar par sjdigest.jar dans la balise <applet>
:
- <applet id="jdigest" code="org.frasq.jdigest.JDigest" archive="sjdigest.jar" width="350" height="30"><param name="traced" value="1"/></applet>
Lancez le panneau de configuration Java :
$ jcontrol
NOTE : Sous Windows, le programme du panneau de configuration s'appelle javacpl
.
Cliquez sur l'onglet Avancé, ouvrez la section Console Java, cochez l'option Afficher la console.
Ouvrez jdigest.html dans votre navigateur préféré :
$ firefox jdigest.html
NOTE : Pour installer le plug-in Java dans Firefox, faites un lien vers le fichier jre/lib/i386/libnpjp2.so du répertoire d'installation du JDK dans le dossier /usr/lib/mozilla/plugins.
$ cd /usr/lib/mozilla/plugins
$ sudo ln -s /usr/java/jre/lib/i386/libnpjp2.so libnpjp2.so
La console Java démarre et une alerte de sécurité est affichée :
Acceptez l'exécution de l'application sans valider l'éditeur. Ouvrez un fichier dans le greffon pour afficher son empreinte.
Appuyez sur x dans la console Java pour vider le cache du chargeur de classes. Actualisez la page JDigest. L'alerte de sécurité revient. Donnez définitivement votre confiance à l'éditeur. Effacez le cache à partir de la console. Actualisez la page. Toutes les applications validées par la signature de l'éditeur s'exécutent sans alerter l'utilisateur et avec les privilèges accordés par défaut.
Lancez le panneau de configuration Java :
$ jcontrol
Cliquez sur l'onglet Sécurité. Affichez les certificats. Vérifiez que votre certificat délivré par l'entité Frasq Signing Authority est dans la liste.
Ajoutez les fichiers jdigestjnlp.html et jdigest.jnlp à la racine du site avec les contenus suivants :
- java/jdigest
- jdigestjnlp.html
- jdigest.jnlp
- sjdigest.jar
- <html>
- <head>
- <title>JDigest</title>
- </head>
- <body>
- <applet jnlp_href="jdigest.jnlp" width="350" height="30"></applet>
- </body>
- </html>
- <?xml version="1.0" encoding="UTF-8"?>
- <jnlp spec="1.0+" codebase="" href="jdigest.jnlp">
- <information>
- <title>JDigest</title>
- <vendor>frasq.org</vendor>
- <offline-allowed/>
- </information>
- <security>
- <all-permissions />
- </security>
- <resources>
- <!-- Application Resources -->
- <j2se version="1.6+" href="http://java.sun.com/products/autodl/j2se" />
- <jar href="sjdigest.jar" />
- </resources>
- <applet-desc
- name="JDigest Applet"
- main-class="org.frasq.jdigest.JDigest"
- width="350"
- height="30">
- </applet-desc>
- <update check="background"/>
- </jnlp>
Ouvrez jdigestjnlp.html dans un navigateur.
Ajoutez le fichier jdigestdeploy.html à la racine du site avec le contenu suivant :
- java/jdigest
- jdigestdeploy.html
- sjdigest.jar
- <html>
- <head>
- <title>JDigest</title>
- </head>
- <body>
- <script type="text/javascript" src="http://www.java.com/js/deployJava.js"></script>
- <script type="text/javascript">
- var attributes = {code:'org.frasq.jdigest.JDigest', archive:'sjdigest.jar', width:350, height:30};
- var parameters = {jnlp_href:'jdigest.jnlp'};
- deployJava.runApplet(attributes, parameters, '1.6');
- </script>
- </body>
- </html>
Ouvrez jdigestdeploy.html dans un navigateur.
Javascript
Ajoutez le fichier JDigestFile.java dans le dossier src/org/frasq/jdigest avec le contenu suivant :
- src/org/frasq/jdigest
- JDigestFile.java
- import java.security.AccessController;
- import java.security.PrivilegedAction;
Importe les classes nécessaires pour accorder des privilèges à un programme.
- public class JDigestFile extends JApplet {
- private static boolean traced = true;
- private static MessageDigest md = null;
JDigestFile
hérite de JApplet
.
Mettez traced
à true
.
- public static String sha1sum(final String filename) {
- return AccessController.doPrivileged(new PrivilegedAction<String>() {
- public String run() {
- try {
- byte[] sum = digestFile(filename);
- if (sum == null)
- return null;
- return bytesToHexString(sum);
- }
- catch (Exception e) {
- trace(e);
- }
- return null;
- }
- });
- }
sha1sum
retourne la représentation en hexadécimal du SHA1 de filename
.
La méthode sha1sum
est appelée par Javascript sans droits spéciaux.
Afin de l'autoriser à ouvrir un fichier local, sha1sum
exécute la méthode digestFile
dans le contexte privilégié de l'applette en enveloppant l'appel dans la méthode doPrivileged
de la classe AccessController
.
- @Override
- public void init() {
- try {
- md = MessageDigest.getInstance("SHA1");
- }
- catch (NoSuchAlgorithmException e) {
- trace(e);
- }
- }
init
crée un MessageDigest
et le sauvegarde dans md
.
Remarquez que l'applette n'affiche rien.
Le reste du code reprend les méthodes trace
, digestFile
et bytesToHexString
.
Fabriquez un jar signé appelé sjdigestfile.jar contenant le code compilé de la classe JDigestFile
:
$ rm -fr bin
$ mkdir bin
$ javac -d bin src/org/frasq/jdigest/JDigestFile.java
$ jar -cvf jdigestfile.jar -C bin org/frasq/jdigest
$ jarsigner -keystore frasq.jks -storepass changeit -signedjar sjdigestfile.jar jdigestfile.jar signer
Ajoutez le fichier jdigestfile.html à la racine du site avec le contenu suivant :
- java/jdigest
- jdigestfile.html
- sjdigestfile.jar
L'expression document.applets['jdigest'].sha1sum(filename.value)
appelle la fonction sha1sum
de l'applette dont l'id
est jdigest
avec la valeur du champ filename
en argument.
Si vous ouvrez jdigestfile.html avec IE, tout se passe bien mais si vous essayez avec Firefox, Opera ou Chrome, vous obtenez une java.io.FileNotFoundException
.
Tracez filename
dans sha1sum
.
Refabriquez le jar signé, effacez le chargeur de classes dans la console Java puis essayez de digérer un fichier dans les différents navigateurs.
IE passe le nom complet à l'applette alors que tous les autres lui passent un nom de fichier incomplet ou maquillé.
Éditez jdigestfile.html et remplacez le type file
de la balise input
du champ digest_file
par text
.
Rechargez le document, tapez directement un nom de fichier complet et appuyez sur Digérer.
Packaging
Ajoutez le fichier build.xml dans le dossier du projet avec le contenu suivant :
- java/jdigest
- build.xml
Éditez les paramètres des cibles init
et signjar
:
- <property name="sourceDir" value="src" />
- <property name="outputDir" value="bin" />
- <property name="package" value="org.frasq.${ant.project.name}" />
- <property name="path" value="org/frasq/${ant.project.name}" />
- <property name="zipFile" value="${ant.project.name}-${project.version}.zip" />
- <target name="sign" description="Builds signed jar" depends="build">
- <signjar jar="${ant.project.name}.jar" signedjar="s${ant.project.name}.jar" keystore="frasq.jks" alias="signer" storepass="changeit" />
- </target>
Remarquez la ligne de configuration qui ajoute gtkjfilechooser.jar au chemin des classes dans le manifeste du jar :
- <jar jarfile="${ant.project.name}.jar">
- <manifest>
- <attribute name="Main-Class" value="${package}.${className}" />
- <attribute name="Class-Path" value="gtkjfilechooser.jar" />
- </manifest>
- <fileset dir="${outputDir}" excludes="**/Thumbs.db" />
- </jar>
Listez toutes les cibles :
$ ant -projecthelp
...
build Builds jar
clean Deletes all generated files but the jars
sign Builds signed jar
test Runs the program
testjar Runs the jar
wipe Deletes the jars and all generated files
zip Packs all the source code in a zip
...
Fabriquez le programme et testez-le :
$ ant test
Testez le jar :
$ ant testjar
Affichez l'empreinte d'un fichier :
$ java -cp bin -jar jdigest.jar jdigest.jar
d1bba1beedaf94ad0904b4fb87c4df70f06edfdc jdigest.jar
Signez le jar :
$ ant sign
Nettoyez le répertoire :
$ ant wipe
Archivez le code source :
$ ant zip
$ unzip -l jdigest-1.1.zip
Commentaires