29

Java plug-ins

Calling a program in Java from a navigator and allowing it to access the local system requires that a whole set of techniques be implemented. Learn how to write a graphical application which can be run as an applet then how to sign it and interface it in HTML and Javascript.

Create a folder called java then a folder jdigest in the java folder.

  1. java
    1. jdigest

In Eclipse, create a Java project called Jdigest in the folder java/jdigest. If you don't use Eclipse, create the following tree:

  1. java/jdigest
    1. bin
    2. src
      1. org
        1. frasq
          1. jdigest
Command

Create the class JDigestCommand in the package org.frasq.jdigest by adding the file JDigestCommand.java in the folder src/org/frasq/jdigest with the following content:

  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;

Defines the program's package. Imports the classes necessary for reading a file and generating a digital imprint.

  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;

Defines the name and the version and revision numbers of the program. traced at true displays the error messages and the trace messages of the program. NOTE: The value of traced can be set directly to true in the code during the development or from the command-line, a configuration file or a call parameter of an applet. md contains the MessageDigest object used by the program to generate a digital imprint.

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

The trace methods display the description of an exception or a plain character string on the error ouput stream if the traced variable is set to 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.     }

Defines the methods which return the name, the version and the revision numbers of the program as well as its full signature.

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

The usage method displays a summary of the different execution options of the program.

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

The program begins by analyzing the command-line to extract the options. -trace sets the variable traced to true. -version displays the signature of the program and exits. Any other option not recognized displays the summary of the different execution options of the program and exits. If the program isn't run with at least one file name in argument, it displays its usage and exits.

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

Goes through the list of parameters following the options, passes each parameter to the digestFile method and displays the generated imprint in hexadecimal with the help of the bytesToHexString method.

  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 returns the SHA1 hashing of the file filename in a byte[]. In case of error, digestFile traces the exceptions and returns null.

The code creates a MessageDigest of type SHA1 on demand and saves it in the static variable md, creates an InputStream called is opened on the file filename, creates a DigestInputStream object called gis with the parameters is and md, reads the file and returns its imprint.

  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 returns a character string containing the representation in hexadecimal of b.

Compile the program while placing the binary in the folder bin:

$ javac -d bin src/org/frasq/jdigest/JDigestCommand.java

Run the program:

$ 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

Compare the result with the output of the sha1sum command:

$ sha1sum bin/org/frasq/jdigest/JDigestCommand.class
f5bdd117deab31fb99f91b44e3b6744036db7f8f  bin/org/frasq/jdigest/JDigestCommand.class
Application

Create the folder src/org/frasq/jdigest/images at the root of the project then copy the files clock_16x16.png and open_16x16.png in the folder images:

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

Add the files parameters.properties and strings.properties in the folder src/org/frasq/jdigest with the following contents:

  1. src/org/frasq/jdigest
    1. parameters.properties
    2. strings.properties
  1. useWindowsLAF=1
  2. useNimbusLAF=0
  3. replaceGtkWithNimbus=0
  4. replaceGtkFileChooser=0

useWindowsLAF at 1 displays the application with the Windows look and feel, if it's available. useNimbusLAF at 1 displays the application with the Nimbus look and feel, normally available on all systems. replaceGtkWithNimbus at 1 displays the application with the Nimbus look and feel if the default look and feel is GTK. replaceGtkFileChooser at 1 replaces the standard FileChooser.

NOTE: To use the replaceGtkFileChooser option, download the archive gtkjfilechooser.jar, copy it in the folder jre/lib/ext of the installation directory of the JDK.

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

Add a version in French:

  1. openToolTip=Ouvrez un fichier

applicationFrameTitle defines the title of the applications displayed in the banner of the window. applicationFrameIcon gives the name of the file containing the icon of the application. openIcon defines the icon of the button which opens the dialog for selecting a file.

IMPORTANT: Save the files in ISO-8859-1.

Remember to copy the folder images and the files parameters.properties and strings.properties in the folder bin/org/frasq/jdigest. NOTE: Eclipse will automatically copy the files before running the program.

In Eclipse, copy the class JDigestCommand with the name JDigestApp. If you don't use Eclipse, edit the file JDigestCommand.java, replace all references to the class JDigestCommand with JDigestApp and save the file as JDigestApp.java in the folder src/org/frasq/jdigest.

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

Edit the file 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.*;

Defines the package of the program. Imports the classes necessary for reading a file, generating a digital imprint and displaying the graphical interface.

  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");

Defines the class JDigestApp which implements the interface ActionListener. Loads the resources defined in the files org/frasq/jdigest/parameters.properties and 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;

Defines the resource names. Defines the variables which contain the different components of the graphical interface.

usage displays one more line:

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

The code of the main method is practically unchanged except to call the method createAndShowGUI if no file name has been passed in argument on the command-line:

  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.         });

Notice how the createAndShowGUI method is called in a separate thread of execution.

  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 initializes the title of the application with getString and the parameter APPLICATIONFRAMETITLE, creates the program instance, adjusts the Look & Feel of the interface with initLookAndFeel, chooses a window decoration integrated to the system, creates the main frame of the application with createFrame and displays it in the middle of the screen.

  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 returns the character string associated to key in the ResourceBundle STRINGS. If key isn't defined in STRINGS, getString returns null, without tracing an error since a resource can be optional.

  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 begins by checking if the Look & Feel Windows and Nimbus are available then, depending on the values of the configuration parameters USEWINDOWSLAF, USENIMBUSLAF, REPLACEGTKWITHNIMBUS and REPLACEGTKFILECHOOSER, modifies the look and feel of the application or, in the last case, the widget which displays the dialog for selecting a file.

  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 returns the character string associated to key in the ResourceBundle PARAMETERS. If key isn't defined in PARAMETERS, getParameter traces the exception and returns fallback.

getBooleanParameter returns false if the configuration parameter key represents the integer 0 or true for any other integer. If key isn't defined in PARAMETERS or if the value of the parameter doesn't represent an integer, getBooleanParameter returns 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 creates a frame with the title applicationTitle, generates the image defined by the parameter APPLICATIONFRAMEICON with createImage and defines it as its icon then creates its content with 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 builds a URL from path and returns the corresponding image generated by the Toolkit. In case of error, createImage traces path and returns 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 returns a panel which aligns a button and a text field. The code creates a JPanel associated to a BoxLayout in LINE_AXIS mode, creates a JButton with createButton placed at the beginning of the line then a JTextField placed at the end of the line whose content is centered and non editable.

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

createButton returns a JButton built by createAbstractButton with the parameters item and 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 returns an AbstractButton of type c and associates it to the ActionListener listener. item is used to read the configuration parameters whose names start with the value of item and end with Label for the label of the button, Icon for the image of its icon and ToolTip for the text of the help bubble. A button can have an icon and a label. A button without a label or an icon has the value of item as a 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.     }

The methods createObject return an object of the class c or whose class is named className.

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

Activating the button OPEN calls the method actionPerformed which calls the method 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 creates a JFileChooser on demand and saves it in the variable chooser. The dialog is centered on the text field. If the return of the dialog is positive, openFile extracts the name of the file from it, checks that the file exists and calls digestFile. If digestFile doesn't send back null, openFile converts the returned byte[] in hexadecimal, traces the imprint, displays it in the text field and copies it in the clipboard.

Compile the program then run it:

$ javac -d bin src/org/frasq/jdigest/JDigestApp.java
$ java -cp bin org.frasq.jdigest.JDigestApp -trace

Under Linux with the GTK Look & Feel from Gnome:

Under Windows with the parameter useWindowsLAF at 1:

Try with the parameter useNimbusLAF at 1:

Click on the button and select a file. The Look & Feel of the dialog differs a lot depending on the versions. The one for the GTK is really pathetic. Try with the parameter replaceGtkWithNimbus at 1 then at 0 with the parameter replaceGtkFileChooser at 1.

NOTE: Remember to copy the file parameters.properties in the folder bin/org/frasq/jdigest every time you modify the source file.

Notice that the command still works:

$ java -cp bin org.frasq.jdigest.JDigestApp bin/org/frasq/jdigest/JDigestApp.class
6ad8ff8b0429d6c28e7518cebb5c2623ece7343b  bin/org/frasq/jdigest/JDigestApp.class
Applet

To transform the application into an applet, copy in Eclipse the class JDigestApp and save it with the name JDigest. If you don't use Eclipse, edit the file JDigestApp.java, replace all the references to the class JDigestApp by JDigest and save the file as JDigest.java in the folder src/org/frasq/jdigest:

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

Edit the file JDigest.java:

  1. public class JDigest extends JApplet implements ActionListener {

The class JDigest inherits from the class JApplet.

Add the init method:

  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 reads the parameter traced passed by the navigator, initializes the Look & Feel of the graphical interface, creates the application panel et defines it as the content of the applet.

Compile the program and build a jar with all the files needed by the application:

$ 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

Create the jar jdigest.jar:

$ jar -cvf jdigest.jar -C bin org/frasq/jdigest

Notice that the application and the command still work:

$ java -jar jdigest.jar jdigest.jar
a2d1c18100345011d808e7327465d59c5fc38363  jdigest.jar
$ java -jar jdigest.jar -trace

Add the file jdigestfail.html at the root of the project with the following content:

  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>

Try the applet:

$ appletviewer jdigestfail.html

Move the pointer of the mouse on the button to display the tooltip. Click. An error is triggered by the security manager:

java.security.AccessControlException: access denied (java.util.PropertyPermission user.home read)

The program isn't authorized to open a local file. You must sign the jar.

Signature

Install OpenSSL:

$ sudo apt-get install openssl

Create a folder called ssl:

$ mkdir ssl

Go in the folder ssl:

$ cd ssl

Create the folders private, certs and newcerts and protect them:

$ mkdir private certs newcerts
$ chmod 700 private certs newcerts

Create the index and initialize the serial number of the certificates:

$ touch index.txt
$ echo 01 > serial

Copy the configuration file /etc/ssl/openssl.cnf:

$ cp /etc/ssl/openssl.cnf .

Edit the file openssl.cnf and change the following lines:

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: The option nsCertType in the section [ usr_cert ] depends on the desired extension. server generates a certificate for a server. client, email generates a user's certificate with an email address. objsign generates a certificate which can be used to sign files.

Begin by creating the certificate of your certification authority:

$ openssl req -config ./openssl.cnf -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 3650

The options -x509 and -extensions v3_ca directly create a self-signed root certificate.

Enter changeit for the password of the key. Give the following answers to the 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

Check the content of the file:

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

Notice that the fields Issuer and Subject are identifical and that the field X509v3 Basic Constraints is set to CA:TRUE.

Create a certificate request:

$ openssl req -config ./openssl.cnf -new -out signer-req.pem -keyout private/signer-key.pem

Enter changeit for the password of the key. Give the following answers to the 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

Sign the certificate request:

$ openssl ca -config ./openssl.cnf -out newcerts/signer-cert.pem -infiles signer-req.pem

Create a certificate with the PKCS12 format:

$ openssl pkcs12 -export -out signer.p12 -in newcerts/signer-cert.pem -inkey private/signer-key.pem
-name "Signer" -caname "Frasq Signing Authority"

Go back in the project's folder:

$ cd ..

Create a Java key store called frasq.jks containing the certificate of the certification authority:

$ keytool -keystore frasq.jks -storepass changeit -importcert -alias ca -file ssl/cacert.pem

Add the certificate and the key of the signer:

$ keytool -importkeystore -srckeystore ssl/signer.p12 -destkeystore frasq.jks -srcstoretype pkcs12

Check the content of the key store:

$ keytool -keystore frasq.jks -storepass changeit -list

Sign the jar:

$ jarsigner -keystore frasq.jks -storepass changeit -signedjar sjdigest.jar jdigest.jar signer

Check the signed jar:

$ jarsigner -keystore frasq.jks -storepass changeit -verify -verbose -certs sjdigest.jar

Read the article The web developer tools for detailed explanations on certificates and communications encryption.

Rename the file jdigestfail.html to jdigest.html:

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

Replace jdigest.jar with sjdigest.jar in the <applet> tag:

<applet id="jdigest" code="org.frasq.jdigest.JDigest" archive="sjdigest.jar" width="350" height="30"><param name="traced" value="1"/></applet>

Launch the Java Control Panel:

$ jcontrol

NOTE: Under Windows, the program of the control panel is called javacpl.

Click on the Advanced tab, open the section Java console, check the option Show console.

Open jdigest.html in your favorite navigator:

$ firefox jdigest.html

NOTE: To install the Java plug-in in Firefox, make a link to the file jre/lib/i386/libnpjp2.so from the JDK installation directory in the folder /usr/lib/mozilla/plugins:

$ cd /usr/lib/mozilla/plugins
$ sudo ln -s /usr/java/jre/lib/i386/libnpjp2.so libnpjp2.so

The Java console starts and a security alert is displayed:

Accept the execution of the application without validating the publisher. Open a file in the plug-in to display its imprint.

Press x in the Java console to clear the class loader cache. Reload the JDigest page. The security alert returns. Agree to always trust content from this publisher. Clear the cache from the console. Reload the page. All the applications validated by the publisher's signature run without alerting the user and with the privileges granted by default.

Launch the Java Control Panel:

$ jcontrol

Click on the Security tab. Display the certificates. Check that your certificate delivered by the entity Frasq Signing Authority is in the list.

Add the files jdigestjnlp.html and jdigest.jnlp at the root of the site with the following contents:

  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>

Open jdigestjnlp.html in a navigator.

Add the file jdigestdeploy.html at the root of the site with the following content:

  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>

Open jdigestdeploy.html in a navigator.

Javascript

Add the file JDigestFile.java in the folder src/org/frasq/jdigest with the following content:

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

Imports the classes necessary to grant privileges to a program.

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

JDigestFile inherits from JApplet. Sets traced to 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 returns the hexadecimal representation of the SHA1 of filename. The sha1sum method is called by Javascript without any special rights. In order to allow it to open a local file, sha1sum runs the digestFile method in the privileged context of the applet by wrapping the call with the doPrivileged method of the AccessController class.

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

init creates a MessageDigest and saves it in md. Notice that the applet doesn't display anything.

The rest of the code repeats the trace, digestFile and bytesToHexString methods.

Build a signed jar called sjdigestfile.jar containing the compiled code of the JDigestFile class:

$ 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

Add the file jdigestfile.html at the root of the site with the following content:

  1. java/jdigest
    1. jdigestfile.html
    2. sjdigestfile.jar
  1. <html>
  2. <head>
  3. <meta http-equiv="content-type" content="text/html; charset=utf-8" lang="en" />
  4. <title>Jdigest</title>
  5. </head>
  6. <body>
  7. <applet id="jdigest" code="org.frasq.jdigest.JDigestFile" archive="sjdigestfile.jar" width="0" height="0"></applet>
  8. <script type="text/javascript">
  9. function digestfile(from, to) {
  10.     var filename = document.getElementById(from);
  11.     if (filename == null)
  12.         return false;
  13.  
  14.     var sha1sum = document.getElementById(to);
  15.     if (sha1sum == null)
  16.         return false;
  17.  
  18.     if (filename.value === '')
  19.         return false;
  20.    
  21.     var s = document.applets['jdigest'].sha1sum(filename.value);
  22.  
  23.     sha1sum.value = s == null ? '' : s;
  24.  
  25.     return true;
  26. }
  27. </script>
  28. <form enctype="multipart/form-data" method="post" action="">
  29. <p>Which document do you want to process?</p>
  30. <p><input name="digest_file" id="digest_file" type="file" size="30"/></p>
  31. <p><input type="button" value="Digest" onclick="digestfile('digest_file', 'digest_sha1');"/></p>
  32. <p><input id="digest_sha1" name="digest_sha1" type="text" size="40" maxlength="40" readonly="readonly" style="font-family:monospace;background-color:#ffffce;"/></p>
  33. <p>Is the SHA1 ready? <input name="digest_send" id="digest_send" type="submit" value="Send" /></p>
  34. </form>
  35. </body>
  36. </html>

The expression document.applets['jdigest'].sha1sum(filename.value) calls the sha1sum function of the applet whose id is jdigest with the value of the field filename in argument.

If you open jdigestfile.html with IE, all is fine but if you try with Firefox, Opera or Chrome, you get a java.io.FileNotFoundException. Trace filename in sha1sum. Rebuild the signed jar, clear the class loader and try to digest a file in the different navigators. IE passes the complete path name to the applet while all the others either pass just the file name or a fake path name.

Edit jdigestfile.html and replace the type file of the input tag of the field digest_file by text. Reload the document, directly type in a complete file name and press Digest.

Packaging

Add the file build.xml in the project's folder with the following content:

  1. java/jdigest
    1. build.xml

Edit the parameters of the targets init and 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>

Notice the configuration line which adds gtkjfilechooser.jar to the class path in the manifest of the 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>

List all the targets:

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

Build the program and test it:

$ ant test

Test the jar:

$ ant testjar

Display the imprint of a file:

$ java -cp bin -jar jdigest.jar jdigest.jar
d1bba1beedaf94ad0904b4fb87c4df70f06edfdc  jdigest.jar

Sign the jar:

$ ant sign

Clean the directory:

$ ant wipe

Archive the source code:

$ ant zip
$ unzip -l jdigest-1.1.zip

Comments

To add a comment, click here.