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.
- java
- jdigest
In Eclipse, create a Java project called Jdigest in the folder java/jdigest. If you don't use Eclipse, create the following tree:
- java/jdigest
- bin
- src
- org
- frasq
- jdigest
- frasq
- org
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:
- 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;
Defines the program's package. Imports the classes necessary for reading a file and generating a digital imprint.
- 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;
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.
- 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);
- }
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
.
- 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() + ")";
- }
Defines the methods which return the name, the version and the revision numbers of the program as well as its full signature.
- public static void usage() {
- System.out.println("jdigest [-trace] file ...");
- System.out.println("jdigest -version");
- }
The usage
method displays a summary of the different execution options of the program.
- 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);
- }
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.
- while (i < args.length) {
- String fname = args[i++];
- byte[] sha1 = digestFile(fname);
- if (sha1 != null) {
- System.out.println(bytesToHexString(sha1) + " " + fname);
- }
- }
- }
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.
- 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
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.
- 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
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:
- src/org/frasq/jdigest
- images
- clock_16x16.png
- open_16x16.png
- images
Add the files parameters.properties and strings.properties in the folder src/org/frasq/jdigest with the following contents:
- src/org/frasq/jdigest
- parameters.properties
- strings.properties
- useWindowsLAF=1
- useNimbusLAF=0
- replaceGtkWithNimbus=0
- 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.
- applicationFrameTitle=Jdigest
- applicationFrameIcon=images/clock_16x16.png
- openIcon=images/open_16x16.png
- openToolTip=Open a file
Add a version in French:
- 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.
- src/org/frasq/jdigest
- JDigestCommand.java
- JDigestApp.java
Edit the file 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.*;
Defines the package of the program. Imports the classes necessary for reading a file, generating a digital imprint and displaying the graphical interface.
- 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");
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.
- 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;
Defines the resource names. Defines the variables which contain the different components of the graphical interface.
usage
displays one more line:
- public static void usage() {
- System.out.println("jdigest [-trace]");
- System.out.println("jdigest [-trace] file ...");
- System.out.println("jdigest -version");
- }
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:
- 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();
- }
- });
Notice how the createAndShowGUI
method is called in a separate thread of execution.
- 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
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.
- public static String getString(String key) {
- try {
- return STRINGS.getString(key);
- }
- catch (java.util.MissingResourceException e) {
- }
- return null;
- }
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.
- 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
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.
- 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
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
.
- 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
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
.
- 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
builds a URL from path
and returns the corresponding image generated by the Toolkit
.
In case of error, createImage
traces path
and returns 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
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.
- public static JButton createButton(String item, ActionListener listener) {
- return (JButton) createAbstractButton(JButton.class, item, listener);
- }
createButton
returns a JButton
built by createAbstractButton
with the parameters item
and 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
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.
- 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;
- }
The methods createObject
return an object of the class c
or whose class is named className
.
- public void actionPerformed(ActionEvent e) {
- String cmd = e.getActionCommand();
- if (cmd == OPEN)
- openFile();
- }
Activating the button OPEN
calls the method actionPerformed
which calls the method 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
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:
- src/org/frasq/jdigest
- JDigestCommand.java
- JDigestApp.java
- JDigest.java
Edit the file JDigest.java:
- public class JDigest extends JApplet implements ActionListener {
The class JDigest
inherits from the class JApplet
.
Add the init
method:
- 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
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:
- 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>
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:
- java/jdigest
- jdigest.html
- sjdigest.jar
Replace jdigest.jar with sjdigest.jar in the <applet>
tag:
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:
- 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>
Open jdigestjnlp.html in a navigator.
Add the file jdigestdeploy.html at the root of the site with the following content:
- 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>
Open jdigestdeploy.html in a navigator.
Javascript
Add the file JDigestFile.java in the folder src/org/frasq/jdigest with the following content:
- src/org/frasq/jdigest
- JDigestFile.java
- import java.security.AccessController;
- import java.security.PrivilegedAction;
Imports the classes necessary to grant privileges to a program.
- public class JDigestFile extends JApplet {
- private static boolean traced = true;
- private static MessageDigest md = null;
JDigestFile
inherits from JApplet
.
Sets traced
to 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
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.
- @Override
- public void init() {
- try {
- md = MessageDigest.getInstance("SHA1");
- }
- catch (NoSuchAlgorithmException e) {
- trace(e);
- }
- }
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:
- java/jdigest
- jdigestfile.html
- sjdigestfile.jar
- <html>
- <head>
- <meta http-equiv="content-type" content="text/html; charset=utf-8" lang="en" />
- <title>Jdigest</title>
- </head>
- <body>
- <applet id="jdigest" code="org.frasq.jdigest.JDigestFile" archive="sjdigestfile.jar" width="0" height="0"></applet>
- <script type="text/javascript">
- function digestfile(from, to) {
- var filename = document.getElementById(from);
- if (filename == null)
- return false;
- var sha1sum = document.getElementById(to);
- if (sha1sum == null)
- return false;
- if (filename.value === '')
- return false;
- var s = document.applets['jdigest'].sha1sum(filename.value);
- sha1sum.value = s == null ? '' : s;
- return true;
- }
- </script>
- <form enctype="multipart/form-data" method="post" action="">
- <p>Which document do you want to process?</p>
- <p><input name="digest_file" id="digest_file" type="file" size="30"/></p>
- <p><input type="button" value="Digest" onclick="digestfile('digest_file', 'digest_sha1');"/></p>
- <p><input id="digest_sha1" name="digest_sha1" type="text" size="40" maxlength="40" readonly="readonly" style="font-family:monospace;background-color:#ffffce;"/></p>
- <p>Is the SHA1 ready? <input name="digest_send" id="digest_send" type="submit" value="Send" /></p>
- </form>
- </body>
- </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:
- java/jdigest
- build.xml
Edit the parameters of the targets init
and 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>
Notice the configuration line which adds gtkjfilechooser.jar to the class path in the manifest of the 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>
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