1
23

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.

  1. java
    1. 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 :

  1. java/jdigest
    1. bin
    2. src
      1. org
        1. frasq
          1. jdigest
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 :

  1. src/org/frasq/jdigest
    1. JDigestCommand.java
  1. package org.frasq.jdigest;
  2.  
  3. import java.io.InputStream;
  4. import java.io.FileInputStream;
  5. import java.io.IOException;
  6.  
  7. import java.security.MessageDigest;
  8. import java.security.DigestInputStream;
  9. 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.

  1. public class JDigestCommand {
  2.     private static final String APPLICATIONNAME = "JDigest";
  3.     private static final String APPLICATIONVERSION = "1.1";
  4.     private static final String APPLICATIONREVISION = "2";
  5.  
  6.     private static MessageDigest md;
  7.  
  8.     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.

  1.     public static void trace(final Exception e) {
  2.         if (traced) {
  3.             try {
  4.                 StackTraceElement s = Thread.currentThread().getStackTrace()[3];
  5.                 String c = Class.forName(s.getClassName()).getSimpleName();
  6.                 String m = s.getMethodName();
  7.                 System.err.println(c + ":" + m + ": " + e);
  8.             }
  9.             catch (Exception none) {
  10.             }
  11.         }
  12.     }
  13.  
  14.     public static void trace(final String s) {
  15.         if (traced)
  16.             System.err.println(s);
  17.     }

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.

  1.     public static String name() {
  2.         return APPLICATIONNAME;
  3.     }
  4.  
  5.     public static String version() {
  6.         return APPLICATIONVERSION;
  7.     }
  8.  
  9.     public static String revision() {
  10.         return APPLICATIONREVISION;
  11.     }
  12.  
  13.     public static String signature() {
  14.         return APPLICATIONNAME + ' ' + version() + " (" + revision() + ")";
  15.     }

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.

  1.     public static void usage() {
  2.         System.out.println("jdigest [-trace] file ...");
  3.         System.out.println("jdigest -version");
  4.     }

La méthode usage affiche un rappel des différentes options d'exécution du programme.

  1.     public static void main(String[] args) {
  2.         int i = 0;
  3.         String arg;
  4.  
  5.         while (i < args.length && args[i].startsWith("-")) {
  6.             arg = args[i++];
  7.  
  8.             if (arg.equals("-trace"))
  9.                 traced = true;
  10.             else if (arg.equals("-version")) {
  11.                 System.out.println(signature());
  12.                 System.exit(0);
  13.             }
  14.             else {
  15.                 usage();
  16.                 System.exit(1);
  17.             }
  18.         }
  19.  
  20.         if (i >= args.length){
  21.             usage();
  22.             System.exit(1);
  23.         }

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.

  1.         while (i < args.length) {
  2.             String fname = args[i++];
  3.             byte[] sha1 = digestFile(fname);
  4.             if (sha1 != null) {
  5.                 System.out.println(bytesToHexString(sha1) + "  " + fname);
  6.             }
  7.         }
  8.     }

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.

  1.     private static byte[] digestFile(String filename) {
  2.         if (md == null) {
  3.             try {
  4.                 md = MessageDigest.getInstance("SHA1");
  5.             }
  6.             catch (NoSuchAlgorithmException e) {
  7.                 trace(e);
  8.             }
  9.         }
  10.  
  11.         if (md != null) {
  12.             try {
  13.                 InputStream is = new FileInputStream(filename);
  14.                 DigestInputStream gis = new DigestInputStream(is, md);
  15.  
  16.                 byte[] buf = new byte[4096];
  17.  
  18.                 while (gis.read(buf) != -1)
  19.                     ;
  20.  
  21.                 return md.digest();
  22.             }
  23.             catch (IOException e) {
  24.                 trace(e);
  25.             }
  26.         }
  27.  
  28.         return null;
  29.     }

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.

  1.     private static char hexdigit[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
  2.  
  3.     private static String bytesToHexString(byte[] b) {
  4.         StringBuffer sb = new StringBuffer();
  5.  
  6.         for (int i = 0; i < b.length; i++) {
  7.             sb.append(hexdigit[(b[i] >> 4) & 0x0f]);
  8.             sb.append(hexdigit[b[i] & 0x0f]);
  9.         }
  10.  
  11.         return sb.toString();
  12.     }
  13. }

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 :

  1. src/org/frasq/jdigest
    1. images
      1. clock_16x16.png
      2. open_16x16.png

Ajoutez les fichiers parameters.properties et strings.properties dans le dossier src/org/frasq/jdigest avec les contenus suivants :

  1. src/org/frasq/jdigest
    1. parameters.properties
    2. strings.properties
  1. useWindowsLAF=1
  2. useNimbusLAF=0
  3. replaceGtkWithNimbus=0
  4. 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.

  1. applicationFrameTitle=Jdigest
  2. applicationFrameIcon=images/clock_16x16.png
  3.  
  4. openIcon=images/open_16x16.png
  5. openToolTip=Open a file

Ajoutez une version en français :

  1. 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.

  1. src/org/frasq/jdigest
    1. JDigestCommand.java
    2. JDigestApp.java

Éditez le fichier JDigestApp.java :

  1. package org.frasq.jdigest;
  2.  
  3. import java.io.File;
  4. import java.io.InputStream;
  5. import java.io.FileInputStream;
  6. import java.io.IOException;
  7.  
  8. import java.security.MessageDigest;
  9. import java.security.DigestInputStream;
  10. import java.security.NoSuchAlgorithmException;
  11.  
  12. import javax.swing.*;
  13. import javax.swing.UIManager.LookAndFeelInfo;
  14.  
  15. import java.awt.*;
  16. import java.awt.event.*;
  17. 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.

  1. public class JDigestApp implements ActionListener {
  2.     private static final java.util.ResourceBundle PARAMETERS = java.util.ResourceBundle.getBundle(JDigestApp.class.getPackage().getName()
  3.             + "." + "parameters");
  4.     private static final java.util.ResourceBundle STRINGS = java.util.ResourceBundle.getBundle(JDigestApp.class.getPackage().getName()
  5.             + "." + "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.

  1.     private static final String APPLICATIONFRAMETITLE = "applicationFrameTitle";
  2.     private static final String APPLICATIONFRAMEICON = "applicationFrameIcon";
  3.  
  4.     private static final String USEWINDOWSLAF = "useWindowsLAF";
  5.     private static final String USENIMBUSLAF = "useNimbusLAF";
  6.     private static final String REPLACEGTKFILECHOOSER = "replaceGtkFileChooser";
  7.     private static final String REPLACEGTKWITHNIMBUS = "replaceGtkWithNimbus";
  8.  
  9.     private static final String OPEN = "open";
  10.  
  11.     private static String applicationTitle;
  12.  
  13.     private JFrame frame;
  14.     private JPanel panel;
  15.     private JButton open;
  16.     private JTextField sha1sum;
  17.     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 :

  1.     public static void usage() {
  2.         System.out.println("jdigest [-trace]");
  3.         System.out.println("jdigest [-trace] file ...");
  4.         System.out.println("jdigest -version");
  5.     }

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 :

  1.         if (i < args.length) {
  2.             while (i < args.length) {
  3.                 String fname = args[i++];
  4.                 byte[] sha1 = digestFile(fname);
  5.                 if (sha1 != null) {
  6.                     System.out.println(bytesToHexString(sha1) + "  " + fname);
  7.                 }
  8.             }
  9.             System.exit(0);
  10.         }
  11.  
  12.         javax.swing.SwingUtilities.invokeLater(new Runnable() {
  13.             @Override
  14.             public void run() {
  15.                 createAndShowGUI();
  16.             }
  17.         });

Remarquez comment la méthode createAndShowGUI est appelée dans un fil d'exécution séparé.

  1.     private static void createAndShowGUI() {
  2.         applicationTitle = getString(APPLICATIONFRAMETITLE);
  3.  
  4.         JDigestApp app = new JDigestApp();
  5.  
  6.         initLookAndFeel();
  7.  
  8.         JFrame.setDefaultLookAndFeelDecorated(false);
  9.         JDialog.setDefaultLookAndFeelDecorated(false);
  10.  
  11.         JFrame frame = app.createFrame();
  12.  
  13.         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  14.  
  15.         frame.setLocationRelativeTo(null);
  16.  
  17.         frame.setVisible(true);
  18.     }

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.

  1.     public static String getString(String key) {
  2.         try {
  3.             return STRINGS.getString(key);
  4.         }
  5.         catch (java.util.MissingResourceException e) {
  6.         }
  7.         return null;
  8.     }

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.

  1.     private static void initLookAndFeel() {
  2.         String lafWindows = null, lafNimbus = null;
  3.  
  4.         UIManager.put("FileChooser.readOnly", Boolean.TRUE);
  5.  
  6.         for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
  7.             if (info.getName() == "Nimbus")
  8.                 lafNimbus = info.getClassName();
  9.             else if (info.getName() == "Windows")
  10.                 lafWindows = info.getClassName();
  11.         }
  12.  
  13.         try {
  14.             if (lafWindows != null && getBooleanParameter(USEWINDOWSLAF, false))
  15.                 UIManager.setLookAndFeel(lafWindows);
  16.             else if (lafNimbus != null && getBooleanParameter(USENIMBUSLAF, false))
  17.                 UIManager.setLookAndFeel(lafNimbus);
  18.             else if (UIManager.getLookAndFeel().getID() == "GTK") {
  19.                 if (lafNimbus != null && getBooleanParameter(REPLACEGTKWITHNIMBUS, false))
  20.                     UIManager.setLookAndFeel(lafNimbus);
  21.                 else if (getBooleanParameter(REPLACEGTKFILECHOOSER, false))
  22.                     UIManager.put("FileChooserUI", "eu.kostia.gtkjfilechooser.ui.GtkFileChooserUI");
  23.             }
  24.         }
  25.         catch (Exception e) {
  26.             trace(e);
  27.         }
  28.     }

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.

  1.     private static String getParameter(String key, String fallback) {
  2.         try {
  3.             return PARAMETERS.getString(key);
  4.         }
  5.         catch (java.util.MissingResourceException e) {
  6.             trace(key + "?");
  7.         }
  8.  
  9.         return fallback;
  10.     }
  11.  
  12.     private static boolean getBooleanParameter(String key, boolean fallback) {
  13.         try {
  14.             String p = getParameter(key, null);
  15.             if (p != null)
  16.                 return Integer.parseInt(p) == 0 ? false : true;
  17.         }
  18.         catch (Exception e) {
  19.             trace(key + "?");
  20.         }
  21.  
  22.         return fallback;
  23.     }

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.

  1.     public JFrame createFrame() {
  2.         frame = new JFrame(applicationTitle);
  3.  
  4.         frame.setIconImage(createImage(getString(APPLICATIONFRAMEICON)));
  5.         frame.setBackground(Color.WHITE);
  6.  
  7.         panel = createPanel();
  8.  
  9.         frame.setContentPane(panel);
  10.  
  11.         frame.pack();
  12.  
  13.         frame.setResizable(false);
  14.  
  15.         return frame;
  16.     }
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.
  1.     public static Image createImage(String path) {
  2.         java.net.URL url = JDigestApp.class.getResource(path);
  3.         if (url == null) {
  4.             trace(path + "?");
  5.             return null;
  6.         }
  7.         return Toolkit.getDefaultToolkit().getImage(url);
  8.     }

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.

  1.     public JPanel createPanel() {
  2.         panel = new JPanel();
  3.         panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
  4.         panel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
  5.  
  6.         open = createButton(OPEN, this);
  7.         open.setPreferredSize(open.getMinimumSize());
  8.  
  9.         panel.add(open, BorderLayout.LINE_START);
  10.  
  11.         sha1sum = new JTextField(40);
  12.         sha1sum.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5));
  13.         sha1sum.setFont(new Font("Monospaced", Font.BOLD, 12));
  14.         sha1sum.setHorizontalAlignment(JTextField.CENTER);
  15.         sha1sum.setEditable(false);
  16.  
  17.         sha1sum.setText("0000000000000000000000000000000000000000");
  18.  
  19.         panel.add(sha1sum, BorderLayout.LINE_END);
  20.  
  21.         return panel;
  22.     }

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.

  1.     public static JButton createButton(String item, ActionListener listener) {
  2.         return (JButton) createAbstractButton(JButton.class, item, listener);
  3.     }

createButton retourne un JButton fabriqué par createAbstractButton avec les paramètres item et listener.

  1.     private static AbstractButton createAbstractButton(Class<? extends AbstractButton> c, String item,
  2.             ActionListener listener) {
  3.         AbstractButton button = (AbstractButton) createObject(c);
  4.  
  5.         String label = getString(item + "Label");
  6.         if (label != null)
  7.             button.setText(label);
  8.         String href = getString(item + "Icon");
  9.         ImageIcon icon = href != null ? createIcon(href) : null;
  10.         if (icon != null)
  11.             button.setIcon(icon);
  12.  
  13.         if (label == null && icon == null)
  14.             button.setText(item);
  15.  
  16.         String tip = getString(item + "ToolTip");
  17.         if (tip != null)
  18.             button.setToolTipText(tip);
  19.  
  20.         button.setFocusPainted(false);
  21.         button.setActionCommand(item);
  22.  
  23.         button.addActionListener(listener);
  24.  
  25.         return button;
  26.     }

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.

  1.     public static Object createObject(Class<?> c) {
  2.         Object object = null;
  3.         try {
  4.             object = c.newInstance();
  5.         }
  6.         catch (InstantiationException e) {
  7.             trace(e);
  8.         }
  9.         catch (IllegalAccessException e) {
  10.             trace(e);
  11.         }
  12.         return object;
  13.     }
  14.  
  15.     public static Object createObject(String className) {
  16.         Object object = null;
  17.         try {
  18.             Class<?> classDefinition = Class.forName(className);
  19.             object = createObject(classDefinition);
  20.         }
  21.         catch (ClassNotFoundException e) {
  22.             trace(e);
  23.         }
  24.         return object;
  25.     }

Les méthodes createObject retournent un objet de la classe c ou dont la classe a pour nom className.

  1.     public void actionPerformed(ActionEvent e) {
  2.         String cmd = e.getActionCommand();
  3.  
  4.         if (cmd == OPEN)
  5.             openFile();
  6.     }

Activer le bouton OPEN appelle la méthode actionPerformed qui appelle la méthode openFile.

  1.     public void openFile() {
  2.         if (chooser == null) {
  3.             chooser = new JFileChooser();
  4.             chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
  5.             chooser.setControlButtonsAreShown(true);
  6.             chooser.setFileHidingEnabled(false);
  7.             chooser.setMultiSelectionEnabled(false);
  8.             chooser.setAcceptAllFileFilterUsed(true);
  9.         }
  10.  
  11.         int r = chooser.showOpenDialog(sha1sum);
  12.  
  13.         if (r == JFileChooser.APPROVE_OPTION) {
  14.             File file = chooser.getSelectedFile();
  15.             if (file.exists()) {
  16.                 byte[] sha1 = digestFile(file.getPath());
  17.                 if (sha1 != null) {
  18.                     String s = bytesToHexString(sha1);
  19.  
  20.                     trace(s + "  " + file.getName());
  21.  
  22.                     sha1sum.setText(s);
  23.  
  24.                     Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
  25.                     Transferable transferable = new StringSelection(s);
  26.                     clipboard.setContents(transferable, null);
  27.                 }
  28.             }
  29.         }
  30.     }

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 :

  1. src/org/frasq/jdigest
    1. JDigestCommand.java
    2. JDigestApp.java
    3. JDigest.java

Éditez le fichier JDigest.java :

  1. public class JDigest extends JApplet implements ActionListener {

La classe JDigest hérite de la classe JApplet.

Ajoutez la méthode init :

  1.     public void init() {
  2.         String p;
  3.        
  4.         try {
  5.             p = getParameter("traced");
  6.             if (p != null)
  7.                 traced = Integer.parseInt(p) == 0 ? false : true;
  8.         }
  9.         catch (java.lang.NumberFormatException e) {
  10.         }
  11.        
  12.         initLookAndFeel();
  13.  
  14.         panel = createPanel();
  15.         panel.setOpaque(true);
  16.  
  17.         setContentPane(panel);
  18.     }

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 :

  1. java/jdigest
    1. jdigestfail.html
    2. jdigest.jar
  1. <html>
  2. <head>
  3. <title>JDigest</title>
  4. </head>
  5. <body>
  6. <applet id="jdigest" code="org.frasq.jdigest.JDigest" archive="jdigest.jar" width="350" height="30"><param name="traced" value="1"/></applet>
  7. </body>
  8. </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 :

  1. java/jdigest
    1. jdigest.html
    2. sjdigest.jar

Remplacez jdigest.jar par sjdigest.jar dans la balise <applet> :

  1. <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 :

  1. java/jdigest
    1. jdigestjnlp.html
    2. jdigest.jnlp
    3. sjdigest.jar
  1. <html>
  2. <head>
  3. <title>JDigest</title>
  4. </head>
  5. <body>
  6. <applet jnlp_href="jdigest.jnlp" width="350" height="30"></applet>
  7. </body>
  8. </html>
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <jnlp spec="1.0+" codebase="" href="jdigest.jnlp">
  3.     <information>
  4.         <title>JDigest</title>
  5.         <vendor>frasq.org</vendor>
  6.         <offline-allowed/>
  7.     </information>
  8.     <security>
  9.     <all-permissions />
  10.     </security>
  11.     <resources>
  12.         <!-- Application Resources -->
  13.         <j2se version="1.6+" href="http://java.sun.com/products/autodl/j2se" />
  14.         <jar href="sjdigest.jar" />
  15.     </resources>
  16.     <applet-desc
  17.         name="JDigest Applet"
  18.         main-class="org.frasq.jdigest.JDigest"
  19.         width="350"
  20.         height="30">
  21.      </applet-desc>
  22.      <update check="background"/>
  23. </jnlp>

Ouvrez jdigestjnlp.html dans un navigateur.

Ajoutez le fichier jdigestdeploy.html à la racine du site avec le contenu suivant :

  1. java/jdigest
    1. jdigestdeploy.html
    2. sjdigest.jar
  1. <html>
  2. <head>
  3. <title>JDigest</title>
  4. </head>
  5. <body>
  6. <script type="text/javascript" src="http://www.java.com/js/deployJava.js"></script>
  7. <script type="text/javascript">
  8. var attributes = {code:'org.frasq.jdigest.JDigest', archive:'sjdigest.jar', width:350, height:30};
  9. var parameters = {jnlp_href:'jdigest.jnlp'};
  10.  
  11. deployJava.runApplet(attributes, parameters, '1.6');
  12. </script>
  13. </body>
  14. </html>

Ouvrez jdigestdeploy.html dans un navigateur.

Javascript

Ajoutez le fichier JDigestFile.java dans le dossier src/org/frasq/jdigest avec le contenu suivant :

  1. src/org/frasq/jdigest
    1. JDigestFile.java
  1. import java.security.AccessController;
  2. import java.security.PrivilegedAction;

Importe les classes nécessaires pour accorder des privilèges à un programme.

  1. public class JDigestFile extends JApplet {
  2.     private static boolean traced = true;
  3.  
  4.     private static MessageDigest md = null;

JDigestFile hérite de JApplet. Mettez traced à true.

  1.     public static String sha1sum(final String filename) {
  2.             return AccessController.doPrivileged(new PrivilegedAction<String>() {
  3.                 public String run() {
  4.                     try {
  5.                         byte[] sum = digestFile(filename);
  6.                         if (sum == null)
  7.                             return null;
  8.                         return bytesToHexString(sum);
  9.                     }
  10.                     catch (Exception e) {
  11.                         trace(e);
  12.                     }
  13.                     return null;
  14.                 }
  15.             });
  16.     }

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.

  1.     @Override
  2.     public void init() {
  3.         try {
  4.             md = MessageDigest.getInstance("SHA1");
  5.         }
  6.         catch (NoSuchAlgorithmException e) {
  7.             trace(e);
  8.         }
  9.     }

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 :

  1. java/jdigest
    1. jdigestfile.html
    2. 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 :

  1. java/jdigest
    1. build.xml

Éditez les paramètres des cibles init et signjar :

  1.         <property name="sourceDir" value="src" />
  2.         <property name="outputDir" value="bin" />
  3.         <property name="package" value="org.frasq.${ant.project.name}" />
  4.         <property name="path" value="org/frasq/${ant.project.name}" />
  5.         <property name="zipFile" value="${ant.project.name}-${project.version}.zip" />
  1.     <target name="sign" description="Builds signed jar" depends="build">
  2.         <signjar jar="${ant.project.name}.jar" signedjar="s${ant.project.name}.jar" keystore="frasq.jks" alias="signer" storepass="changeit" />
  3.     </target>

Remarquez la ligne de configuration qui ajoute gtkjfilechooser.jar au chemin des classes dans le manifeste du jar :

  1.         <jar jarfile="${ant.project.name}.jar">
  2.             <manifest>
  3.                 <attribute name="Main-Class" value="${package}.${className}" />
  4.                 <attribute name="Class-Path" value="gtkjfilechooser.jar" />
  5.             </manifest>
  6.             <fileset dir="${outputDir}" excludes="**/Thumbs.db" />
  7.         </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

Pour ajouter un commentaire, cliquez ici.