changeset 828:bc9e89ad95e5 cacao

2008-02-19 Joshua Sumali <jsumali@redhat.com> * tools/netx/jnlp/resources/Messages.properties: Added new messages for trusted and untrusted certificates. * tools/netx/jnlp/runtime/JNLPClassloader.java: Fixed code for showing proper warning dialogs. * tools/netx/jnlp/security/CertsInfoPane.java: Added SHA-1 and MD5 certificate fingerprints. * tools/netx/jnlp/security/MoreInfoPane.java (actionPerformed(e)): Changed getCerts() to getJarSigner() * tools/netx/jnlp/security/SecurityWarningDialog.java (DialogType): Added a new type of warning. (certs): Removed this field and (details): this field in favor of ... (jarSigner): this new field. (showAccessWarningDialog(accessType, file): Rewritten to show new warning type. (showMoreInfoDialog): Changed method signature to above field change. (showCertInfoDialog): Likewise. (getJarSigner): New Method. (updateUI): Added extra case for new warning type. * tools/netx/jnlp/services/ServiceUtil.java: Added extra parameter to method call for changes in SecurityWarningDialog. * tools/netx/jnlp/tools/JarSigner.java (allVerified): Field refactored to ... (alreadyTrustPublisher): This. (rootInCacerts): New Field. (certPath): Likewise. (noSigningIssues): Likewise. (getAlreadyTrustPublisher): New method. (getRootInCacerts): Likewise. (getCertPath): Likewise. (allVerified): Method refactored to ... (noSigningIssues): This. (checkTrustedCerts): New method. (getPublisher): Likewise. (getRoot): Likewise. * tools/netx/jnlp/security/AccessWarningPane.java: New file. * tools/netx/jnlp/security/CertWarningPane.java: New file. * tools/netx/jnlp/tools/KeyTool.java: New file. * tools/netx/jnlp/security/SecurityWarningOptionPane.java: Removed since this class was split into AccessWarningPane and CertWarningPane.
author Joshua Sumali <jsumali@redhat.com>
date Tue, 19 Feb 2008 13:34:04 -0500
parents 0eb60a3cf6f0
children 931592e5eab7
files ChangeLog tools/netx/jnlp/resources/Messages.properties tools/netx/jnlp/runtime/JNLPClassLoader.java tools/netx/jnlp/security/AccessWarningPane.java tools/netx/jnlp/security/CertWarningPane.java tools/netx/jnlp/security/CertsInfoPane.java tools/netx/jnlp/security/MoreInfoPane.java tools/netx/jnlp/security/SecurityWarningDialog.java tools/netx/jnlp/security/SecurityWarningOptionPane.java tools/netx/jnlp/services/ServiceUtil.java tools/netx/jnlp/tools/JarSigner.java tools/netx/jnlp/tools/KeyTool.java
diffstat 12 files changed, 1268 insertions(+), 345 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Mon Feb 18 10:36:18 2008 -0500
+++ b/ChangeLog	Tue Feb 19 13:34:04 2008 -0500
@@ -1,3 +1,46 @@
+2008-02-19  Joshua Sumali  <jsumali@redhat.com>
+
+	* tools/netx/jnlp/resources/Messages.properties: Added new messages for
+	trusted and untrusted certificates.
+	* tools/netx/jnlp/runtime/JNLPClassloader.java: Fixed code for showing
+	proper warning dialogs.
+	* tools/netx/jnlp/security/CertsInfoPane.java: Added SHA-1 and MD5
+	certificate fingerprints.
+	* tools/netx/jnlp/security/MoreInfoPane.java
+	(actionPerformed(e)): Changed getCerts() to getJarSigner()
+	* tools/netx/jnlp/security/SecurityWarningDialog.java
+	(DialogType): Added a new type of warning.
+	(certs): Removed this field and
+	(details): this field in favor of ...
+	(jarSigner): this new field.
+	(showAccessWarningDialog(accessType, file): Rewritten to show new warning
+	type.
+	(showMoreInfoDialog): Changed method signature to above field change.
+	(showCertInfoDialog): Likewise.
+	(getJarSigner): New Method.
+	(updateUI): Added extra case for new warning type.
+	* tools/netx/jnlp/services/ServiceUtil.java: Added extra parameter to
+	method call for	changes in SecurityWarningDialog.
+	* tools/netx/jnlp/tools/JarSigner.java
+	(allVerified): Field refactored to ...
+	(alreadyTrustPublisher): This.
+	(rootInCacerts): New Field.
+	(certPath): Likewise.
+	(noSigningIssues): Likewise.
+	(getAlreadyTrustPublisher): New method.
+	(getRootInCacerts): Likewise.
+	(getCertPath): Likewise.
+	(allVerified): Method refactored to ...
+	(noSigningIssues): This. 
+	(checkTrustedCerts): New method.
+	(getPublisher): Likewise.
+	(getRoot): Likewise.
+	* tools/netx/jnlp/security/AccessWarningPane.java: New file.
+	* tools/netx/jnlp/security/CertWarningPane.java: New file.
+	* tools/netx/jnlp/tools/KeyTool.java: New file.
+	* tools/netx/jnlp/security/SecurityWarningOptionPane.java: Removed since
+	this class was split into AccessWarningPane and CertWarningPane.
+
 2008-02-18  Lillian Angel  <langel@redhat.com>
 
 	* Makefile.am: Added icedtea-always-zero.patch to DIST.
--- a/tools/netx/jnlp/resources/Messages.properties	Mon Feb 18 10:36:18 2008 -0500
+++ b/tools/netx/jnlp/resources/Messages.properties	Tue Feb 19 13:34:04 2008 -0500
@@ -135,6 +135,7 @@
 SFileWriteAccess=The application has requested write access to a file on the machine. Do you want to allow this action?
 SSigUnverified=The application's digital signature cannot be verified. Do you want to run the application?
 SSigVerified=The application's digital signature has been verified. Do you want to run the application?
+SSignatureError=The application's digital signature has an error. Do you want to run the application?
 SUntrustedSource=The digital signature could not be verified by a trusted source. Only run if you trust the origin of the application.
 STrustedSource=The digital signature has been validated by a trusted source.
 SClipboardReadAccess=The application has requested read-only access to the system clipboard. Do you want to allow this action?
@@ -149,6 +150,8 @@
 SHasExpiredCert=The digital signature has expired.
 SHasExpiringCert=Resources contain entries whose signer certificate will expire within six months.
 SNotYetValidCert=Resources contain entries whose signer certificate is not yet valid.
+SUntrustedCertificate=The digital signature was generated with an untrusted certificate.
+STrustedCertificate=The digital signature was generated with a trusted certificate.
 SRunWithoutRestrictions=This application will be run without the security restrictions normally provided by java.
-SRunWithUntrustedCertificate=The digital signature was generated with an untrusted certificate.
 
+
--- a/tools/netx/jnlp/runtime/JNLPClassLoader.java	Mon Feb 18 10:36:18 2008 -0500
+++ b/tools/netx/jnlp/runtime/JNLPClassLoader.java	Tue Feb 19 13:34:04 2008 -0500
@@ -25,10 +25,12 @@
 import java.lang.reflect.*;
 import javax.jnlp.*;
 import javax.swing.JOptionPane;
+import java.security.cert.Certificate;
 
 import netx.jnlp.cache.*;
 import netx.jnlp.*;
 import netx.jnlp.tools.JarSigner;
+import netx.jnlp.tools.KeyTool;
 import netx.jnlp.services.*;
 import netx.jnlp.security.*;
 
@@ -233,7 +235,6 @@
         if (strict)
             fillInPartJars(initialJars); // add in each initial part's lazy jars
 
-		//Verify jars if the -verify option is passed.
 		if (JNLPRuntime.isVerifying()) {
 
 			JarSigner js;
@@ -243,6 +244,8 @@
 				js = verifyJars(initialJars);
 			} catch (Exception e) {
 				//we caught an Exception from the JarSigner class.
+				//Note: one of these exceptions could be from not being able
+				//to read the cacerts or trusted.certs files.
 				e.printStackTrace();
 				throw new LaunchException(null, null, R("LSFatal"),
 					R("LCInit"), R("LFatalVerification"), R("LFatalVerificationInfo"));
@@ -251,25 +254,32 @@
 			//Case when at least one jar has some signing
 			if (js.anyJarsSigned()){
 				signing = true;
-				//if there was some problem with the signing...
-				if (!js.allVerified()) {
 
-					boolean b = SecurityWarningDialog.showWarningDialog(
-						SecurityWarningDialog.AccessType.UNVERIFIED, file,
-						js.getCerts(), js.getDetails());
-					if (!b)
-						throw new LaunchException(null, null, R("LSFatal"), 
-							R("LCLaunching"), R("LNotVerified"), "");
+				//user does not trust this publisher
+				if (!js.getAlreadyTrustPublisher()) {
+					if (!js.getRootInCacerts()) { //root cert is not in cacerts
+						boolean b = SecurityWarningDialog.showCertWarningDialog(
+							SecurityWarningDialog.AccessType.UNVERIFIED, file, js);
+						if (!b)
+							throw new LaunchException(null, null, R("LSFatal"), 
+								R("LCLaunching"), R("LNotVerified"), "");
+					} else if (js.getRootInCacerts()) { //root cert is in cacerts
+						boolean b = false;
+						if (js.noSigningIssues())
+							b = SecurityWarningDialog.showCertWarningDialog(
+									SecurityWarningDialog.AccessType.VERIFIED, file, js);
+						else if (!js.noSigningIssues())
+							b = SecurityWarningDialog.showCertWarningDialog(
+									SecurityWarningDialog.AccessType.SIGNING_ERROR, file, js);
+						if (!b)
+							throw new LaunchException(null, null, R("LSFatal"),
+								R("LCLaunching"), R("LCancelOnUserRequest"), "");
+					}
 				} else {
-					//jar is completely verified, but we still need to show
-					//a dialog
-
-					boolean b = SecurityWarningDialog.showWarningDialog(
-						SecurityWarningDialog.AccessType.VERIFIED, file,
-						js.getCerts(), js.getDetails());
-					if (!b)
-						throw new LaunchException(null, null, R("LSFatal"),
-							R("LCLaunching"), R("LCancelOnUserRequest"), "");
+					/**
+					 * If the user trusts this publisher (i.e. the publisher's certificate
+					 * is in the user's trusted.certs file), we do not show any dialogs.
+					 */
 				}
 			} else {
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/netx/jnlp/security/AccessWarningPane.java	Tue Feb 19 13:34:04 2008 -0500
@@ -0,0 +1,199 @@
+/* AccessWarningPane.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+*/
+
+package netx.jnlp.security;
+
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.plaf.OptionPaneUI;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.ComponentAdapter;
+import java.util.List;
+import java.security.cert.Certificate;
+import java.security.cert.CertPath;
+import sun.swing.DefaultLookup;
+import netx.jnlp.runtime.JNLPRuntime;
+import netx.jnlp.JNLPFile;
+import netx.jnlp.tools.KeyTool;
+
+/**
+ * Provides the look and feel for a SecurityWarningDialog. These dialogs are
+ * used to warn the user when either signed code (with or without signing 
+ * issues) is going to be run, or when service permission (file, clipboard,
+ * printer, etc) is needed with unsigned code.
+ *
+ * @author <a href="mailto:jsumali@redhat.com">Joshua Sumali</a>
+ */
+public class AccessWarningPane extends SecurityDialogUI {
+
+	JCheckBox alwaysAllow;
+
+	public AccessWarningPane(JComponent x) {
+		super(x);
+	}
+
+	/**
+	 * Creates the actual GUI components, and adds it to <code>optionPane</code>
+	 */
+	protected void installComponents() {
+		SecurityWarningDialog.AccessType type =
+		    ((SecurityWarningDialog)optionPane).getType();
+		JNLPFile file =
+		    ((SecurityWarningDialog)optionPane).getFile();
+
+		String name = "";
+		String publisher = "";
+		String from = "";
+
+		//We don't worry about exceptions when trying to fill in
+		//these strings -- we just want to fill in as many as possible.
+		try {
+			name = file.getInformation().getTitle();
+		} catch (Exception e) {
+		}
+
+		try {
+			publisher = file.getInformation().getVendor();
+		} catch (Exception e) {
+		}
+
+		try {
+			from = file.getInformation().getHomepage().toString();
+		} catch (Exception e) {
+		}
+
+		//Top label
+		String topLabelText = "";
+		String propertyName = "";
+		switch (type) {
+			case READ_FILE:
+				topLabelText = R("SFileReadAccess");
+				propertyName = "OptionPane.warningIcon";
+				break;
+			case WRITE_FILE:
+				topLabelText = R("SFileWriteAccess");
+				propertyName = "OptionPane.warningIcon";
+				break;
+			case CLIPBOARD_READ:
+				topLabelText = R("SClipboardReadAccess");
+				propertyName = "OptionPane.warningIcon";
+				break;
+			case CLIPBOARD_WRITE:
+				topLabelText = R("SClipboardWriteAccess");
+				propertyName = "OptionPane.warningIcon";
+				break;
+			case PRINTER:
+				topLabelText = R("SPrinterAccess");
+				propertyName = "OptionPane.warningIcon";
+				break;
+		}
+		
+		//TODO: Get system icons and add them to our dialogs.
+		//Icon icon = (Icon)DefaultLookup.get(optionPane,this,propertyName);
+		JLabel topLabel = new JLabel(htmlWrap(topLabelText));
+		topLabel.setFont(new Font(topLabel.getFont().toString(), 
+			Font.BOLD, 12));
+		JPanel topPanel = new JPanel(new BorderLayout());
+		topPanel.setBackground(Color.WHITE);
+		topPanel.add(topLabel, BorderLayout.CENTER);
+		topPanel.setPreferredSize(new Dimension(400,60));
+		topPanel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
+
+		//application info
+		JLabel nameLabel = new JLabel("Name:   " + name);
+		nameLabel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+		JLabel publisherLabel = new JLabel("Publisher: " + publisher);
+		publisherLabel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+		JLabel fromLabel = new JLabel("From:   " + from);
+		fromLabel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+
+		alwaysAllow = new JCheckBox("Always allow this action");
+		alwaysAllow.setEnabled(false);
+
+		JPanel infoPanel = new JPanel(new GridLayout(4,1));
+		infoPanel.add(nameLabel);
+		infoPanel.add(publisherLabel);
+		infoPanel.add(fromLabel);
+		infoPanel.add(alwaysAllow);
+		infoPanel.setBorder(BorderFactory.createEmptyBorder(25,25,25,25));
+
+		//run and cancel buttons
+		JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+		
+		JButton run = new JButton("Allow");
+		JButton cancel = new JButton("Cancel");
+		run.addActionListener(createButtonActionListener(0));
+		run.addActionListener(new CheckBoxListener());
+		cancel.addActionListener(createButtonActionListener(1));
+		initialFocusComponent = cancel;
+		buttonPanel.add(run);
+		buttonPanel.add(cancel);
+		buttonPanel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
+
+		//all of the above
+		JPanel main = new JPanel();
+		main.setLayout(new BoxLayout(main, BoxLayout.Y_AXIS));
+		main.add(topPanel);
+		main.add(infoPanel);
+		main.add(buttonPanel);
+
+		optionPane.add(main, BorderLayout.CENTER);
+	}
+
+	private static String R(String key) {
+        return JNLPRuntime.getMessage(key);
+    }
+
+	protected String htmlWrap (String s) {
+        return "<html>"+s+"</html>";
+    }
+
+	private class CheckBoxListener implements ActionListener {
+		public void actionPerformed(ActionEvent e) {
+			if (alwaysAllow != null && alwaysAllow.isSelected()) {
+				// TODO: somehow tell the ApplicationInstance
+				// to stop asking for permission
+			}
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/netx/jnlp/security/CertWarningPane.java	Tue Feb 19 13:34:04 2008 -0500
@@ -0,0 +1,223 @@
+/* CertWarningPane.java
+   Copyright (C) 2008 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 2.
+
+IcedTea is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version.
+*/
+
+package netx.jnlp.security;
+
+import java.awt.*;
+import javax.swing.*;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.security.cert.Certificate;
+import netx.jnlp.runtime.JNLPRuntime;
+import netx.jnlp.JNLPFile;
+import netx.jnlp.tools.KeyTool;
+
+/**
+ * Provides the look and feel for a SecurityWarningDialog. These dialogs are
+ * used to warn the user when either signed code (with or without signing 
+ * issues) is going to be run, or when service permission (file, clipboard,
+ * printer, etc) is needed with unsigned code.
+ *
+ * @author <a href="mailto:jsumali@redhat.com">Joshua Sumali</a>
+ */
+public class CertWarningPane extends SecurityDialogUI {
+
+	JCheckBox alwaysTrust;
+
+	public CertWarningPane(JComponent x) {
+		super(x);
+	}
+
+	/**
+	 * Creates the actual GUI components, and adds it to <code>optionPane</code>
+	 */
+	protected void installComponents() {
+		SecurityWarningDialog.AccessType type =
+			((SecurityWarningDialog)optionPane).getType();
+		JNLPFile file =
+			((SecurityWarningDialog)optionPane).getFile();
+
+		String name = "";
+		String publisher = "";
+		String from = "";
+
+		//We don't worry about exceptions when trying to fill in
+		//these strings -- we just want to fill in as many as possible.
+		try {
+			name = file.getInformation().getTitle();
+		} catch (Exception e) {
+		}
+
+		try {
+			publisher = file.getInformation().getVendor();
+		} catch (Exception e) {
+		}
+
+		try {
+			from = file.getInformation().getHomepage().toString();
+		} catch (Exception e) {
+		}
+
+		//Top label
+		String topLabelText = "";
+		String propertyName = "";
+		switch (type) {
+		case VERIFIED:
+			topLabelText = R("SSigVerified");
+			propertyName = "OptionPane.informationIcon";
+			break;
+		case UNVERIFIED:
+			topLabelText = R("SSigUnverified");
+			propertyName = "OptionPane.warningIcon";
+			break;
+		case SIGNING_ERROR:
+			topLabelText = R("SSignatureError");
+			propertyName = "OptionPane.warningIcon";
+			break;
+		}
+		//TODO: Get system icons and add them to our dialogs.
+		//Icon icon = (Icon)DefaultLookup.get(optionPane,this,propertyName);
+		JLabel topLabel = new JLabel(htmlWrap(topLabelText));
+		topLabel.setFont(new Font(topLabel.getFont().toString(), 
+				Font.BOLD, 12));
+		JPanel topPanel = new JPanel(new BorderLayout());
+		topPanel.setBackground(Color.WHITE);
+		topPanel.add(topLabel, BorderLayout.CENTER);
+		topPanel.setPreferredSize(new Dimension(400,60));
+		topPanel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
+
+		//application info
+		JLabel nameLabel = new JLabel("Name:   " + name);
+		nameLabel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+		JLabel publisherLabel = new JLabel("Publisher: " + publisher);
+		publisherLabel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+		JLabel fromLabel = new JLabel("From:   " + from);
+		fromLabel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+
+		alwaysTrust = new JCheckBox(
+		"Always trust content from this publisher");
+		alwaysTrust.setEnabled(true);
+
+		JPanel infoPanel = new JPanel(new GridLayout(4,1));
+		infoPanel.add(nameLabel);
+		infoPanel.add(publisherLabel);
+		infoPanel.add(fromLabel);
+		infoPanel.add(alwaysTrust);
+		infoPanel.setBorder(BorderFactory.createEmptyBorder(25,25,25,25));
+
+		//run and cancel buttons
+		JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+
+		String runButtonText = "Run";
+
+		JButton run = new JButton(runButtonText);
+		JButton cancel = new JButton("Cancel");
+		run.addActionListener(createButtonActionListener(0));
+		run.addActionListener(new CheckBoxListener());
+		cancel.addActionListener(createButtonActionListener(1));
+		initialFocusComponent = cancel;
+		buttonPanel.add(run);
+		buttonPanel.add(cancel);
+		buttonPanel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
+
+		//all of the above
+		JPanel main = new JPanel();
+		main.setLayout(new BoxLayout(main, BoxLayout.Y_AXIS));
+		main.add(topPanel);
+		main.add(infoPanel);
+		main.add(buttonPanel);
+
+		JLabel bottomLabel;
+		JButton moreInfo = new JButton("More information...");
+		moreInfo.addActionListener(new MoreInfoButtonListener());
+		
+		//TODO: This should check if the X500Issuer is in the cacerts file.
+		if (((SecurityWarningDialog)optionPane).getJarSigner().getRootInCacerts())
+			bottomLabel = new JLabel(htmlWrap(R("STrustedSource")));
+		else
+			bottomLabel = new JLabel(htmlWrap(R("SUntrustedSource")));
+
+		JPanel bottomPanel = new JPanel();
+		bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.X_AXIS));
+		bottomPanel.add(bottomLabel);
+		bottomPanel.add(moreInfo);
+		bottomPanel.setPreferredSize(new Dimension(500,100));
+		bottomPanel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
+		main.add(bottomPanel);
+
+		optionPane.add(main, BorderLayout.CENTER);
+	}
+
+	private static String R(String key) {
+        return JNLPRuntime.getMessage(key);
+    }
+
+	protected String htmlWrap (String s) {
+        return "<html>"+s+"</html>";
+    }
+
+	private class MoreInfoButtonListener implements ActionListener {
+		public void actionPerformed(ActionEvent e) {
+			
+			// TODO: Can we change this to just
+			// optionPane.showMoreInfo(); ?
+			SecurityWarningDialog.showMoreInfoDialog(
+				((SecurityWarningDialog)optionPane).getJarSigner(), 
+				optionPane);
+		}
+	}
+
+	/**
+	 * Updates the user's KeyStore of trusted Certificates.
+	 */
+	private class CheckBoxListener implements ActionListener {
+		public void actionPerformed(ActionEvent e) {
+			if (alwaysTrust != null && alwaysTrust.isSelected()) {
+				try {
+					KeyTool kt = new KeyTool();
+					Certificate c =
+						((SecurityWarningDialog)optionPane).getJarSigner().getRoot();
+					kt.importCert(c);
+				} catch (Exception ex) {
+					//TODO: Let NetX show a dialog here notifying user 
+					//about being unable to add cert to keystore
+				}
+			}
+		}
+	}
+
+}
--- a/tools/netx/jnlp/security/CertsInfoPane.java	Mon Feb 18 10:36:18 2008 -0500
+++ b/tools/netx/jnlp/security/CertsInfoPane.java	Tue Feb 19 13:34:04 2008 -0500
@@ -41,6 +41,7 @@
 import java.util.Date;
 import java.security.cert.CertPath;
 import java.security.cert.X509Certificate;
+import java.security.MessageDigest;
 import java.math.BigInteger;
 import javax.security.auth.x500.X500Principal;
 import sun.security.x509.*;
@@ -97,7 +98,8 @@
 				+ " (" + issuerString + ")");
 
 		//not self signed
-		if (firstPath.getCertificates().size() > 1) {
+		if (!firstCert.getSubjectDN().equals(firstCert.getIssuerDN()) 
+			&& (firstPath.getCertificates().size() > 1)) {
 			X509Certificate secondCert = 
 				((X509Certificate)firstPath.getCertificates().get(1));
 			subjectString = 
@@ -118,7 +120,7 @@
 	 * Constructs the GUI components of this UI
 	 */
 	protected void installComponents() {
-		certs = ((SecurityWarningDialog)optionPane).getCerts();
+		certs = ((SecurityWarningDialog)optionPane).getJarSigner().getCerts();
 		buildTree();
 		certNames = new String[certs.get(0).getCertificates().size()];
 		certsData = new ArrayList<String[][]>();
@@ -139,13 +141,30 @@
             HexDumpEncoder encoder = new HexDumpEncoder();
             String signature = encoder.encodeBuffer(c.getSignature());
 
+			String md5Hash = "";
+			String sha1Hash = "";
+			try {
+				MessageDigest digest = MessageDigest.getInstance("MD5");
+				digest.update(c.getEncoded());
+				md5Hash = makeFingerprint(digest.digest());
+
+				digest = MessageDigest.getInstance("SHA-1");
+				digest.update(c.getEncoded());
+				sha1Hash = makeFingerprint(digest.digest());
+			} catch (Exception e) {
+				//fail quietly
+			}
+
             String[][] cert = { {"Version", version},
                                 {"Serial", serialNumber},
                                 {"Signature Algorithm", signatureAlg},
                                 {"Issuer", issuer},
                                 {"Validity", validity},
                                 {"Subject", subject},
-                                {"Signature", signature} };
+                                {"Signature", signature},
+								{"MD5 Fingerprint", md5Hash},
+								{"SHA1 Fingerprint", sha1Hash}
+								};
             certsData.add(cert);
             certNames[i] = getCN(c.getSubjectX500Principal().getName())
 				+ " (" + getCN(c.getIssuerX500Principal().getName()) + ")";
@@ -309,4 +328,19 @@
             }
         }
     }
+
+	/**
+	 * Makes a human readable hash fingerprint.
+	 * For example: 11:22:33:44:AA:BB:CC:DD:EE:FF.
+	 */
+	private String makeFingerprint(byte[] hash) {
+		String fingerprint = "";
+		for (int i = 0; i < hash.length; i++) {
+			if (!fingerprint.equals(""))
+				fingerprint += ":";
+			fingerprint += Integer.toHexString(
+				((hash[i] & 0xFF)|0x100)).substring(1,3);
+		}
+		return fingerprint.toUpperCase();
+	}
 }
--- a/tools/netx/jnlp/security/MoreInfoPane.java	Mon Feb 18 10:36:18 2008 -0500
+++ b/tools/netx/jnlp/security/MoreInfoPane.java	Tue Feb 19 13:34:04 2008 -0500
@@ -62,7 +62,8 @@
 	 */
 	protected void installComponents() {
 		ArrayList<String> details = 
-			((SecurityWarningDialog)optionPane).getDetails();
+			((SecurityWarningDialog)optionPane)
+				.getJarSigner().getDetails();
 
 		int numLabels = details.size();
 		JPanel errorPanel = new JPanel(new GridLayout(numLabels,1));
@@ -98,8 +99,9 @@
 
 	private class CertInfoButtonListener implements ActionListener {
         public void actionPerformed(ActionEvent e) {
+        	//TODO: Change to ((SecurityWarningDialog) optionPane).showCertInfoDialog()
             SecurityWarningDialog.showCertInfoDialog(
-				((SecurityWarningDialog)optionPane).getCerts(),
+				((SecurityWarningDialog)optionPane).getJarSigner(),
 				optionPane);
         }
     }
--- a/tools/netx/jnlp/security/SecurityWarningDialog.java	Mon Feb 18 10:36:18 2008 -0500
+++ b/tools/netx/jnlp/security/SecurityWarningDialog.java	Tue Feb 19 13:34:04 2008 -0500
@@ -46,6 +46,7 @@
 import java.beans.PropertyChangeEvent;
 import java.util.ArrayList;
 import java.security.cert.CertPath;
+import netx.jnlp.tools.JarSigner;
 
 /**
  * Provides methods for showing security warning dialogs
@@ -57,10 +58,12 @@
 
 	/** Types of dialogs we can create */
 	public static enum DialogType {
-		WARNING,
+		CERT_WARNING,
 		MORE_INFO,
-		CERT_INFO
+		CERT_INFO,
+		ACCESS_WARNING
 	}
+	
 	/** The types of access which may need user permission. */
 	public static enum AccessType {
         READ_FILE,
@@ -69,7 +72,8 @@
         CLIPBOARD_WRITE,
         PRINTER,
         VERIFIED,
-        UNVERIFIED
+        UNVERIFIED,
+        SIGNING_ERROR
     }
 
 	/** The type of dialog we want to show */
@@ -81,57 +85,54 @@
 	/** The application file assocated with this security warning */
 	private JNLPFile file;
 
-	/** The Certificates associated with this application */
-	private ArrayList<CertPath> certs;
-
-	/** Details of the signing */
-	private ArrayList<String> details;
-
+	private JarSigner jarSigner;
+	
 	/** Whether or not this object has been fully initialized */
 	private boolean initialized = false;
 
 	public SecurityWarningDialog(DialogType dialogType, AccessType accessType,
-		JNLPFile file, ArrayList<CertPath> certs, 
-		ArrayList<String> details) {
+			JNLPFile file) {
 		this.dialogType = dialogType;
 		this.accessType = accessType;
 		this.file = file;
-		this.certs = certs;
-		this.details = details;
+		this.jarSigner = null;
 		initialized = true;
 		updateUI();
 	}
-
+	
+	public SecurityWarningDialog(DialogType dialogType, AccessType accessType,
+			JNLPFile file, JarSigner jarSigner) {
+			this.dialogType = dialogType;
+			this.accessType = accessType;
+			this.file = file;
+			this.jarSigner = jarSigner;
+			initialized = true;
+			updateUI();
+	}
+	
+	/**
+	 * Returns if this dialog has been fully initialized yet.
+	 * @return true if this dialog has been initialized, and false otherwise.
+	 */
 	public boolean isInitialized(){
 		return initialized;
 	}
 	
-	public static boolean showWarningDialog(AccessType accessType, 
+	/**
+	 * Shows a warning dialog for different types of system access (i.e. file
+	 * open/save, clipboard read/write, printing, etc).
+	 * 
+	 * @param accessType the type of system access requested.
+	 * @param file the jnlp file associated with the requesting application.
+	 * @return true if permission was granted by the user, false otherwise.
+	 */
+	public static boolean showAccessWarningDialog(AccessType accessType, 
 		JNLPFile file) {
-	 	return showWarningDialog(accessType, file, null, null);
-	}
-
-	/**
-	 * Shows a security warning dialog according to the specified type of
-	 * access. If <code>type</code> is one of AccessType.VERIFIED or
-	 * AccessType.UNVERIFIED, extra details will be available with regards
-	 * to code signing and signing certificates.
-	 *
-	 * @param accessType the type of warning dialog to show
-	 * @param file the JNLPFile associated with this warning
-	 * @param certs the signing certificates assocated with this warning, 
-	 * if any. Can be null.
-	 * @param details the extra details associated with this warning. Can be null.
-	 */
-	public static boolean showWarningDialog(AccessType accessType, 
-		JNLPFile file, ArrayList<CertPath> certs, 
-		ArrayList<String> details) {
-		SecurityWarningDialog swd = 
-			new SecurityWarningDialog(DialogType.WARNING, accessType, file,
-			certs, details);
+		SecurityWarningDialog swd = new SecurityWarningDialog(
+				DialogType.ACCESS_WARNING, accessType, file);
 		JDialog dialog = swd.createDialog();
 		swd.selectInitialValue();
-		dialog.setResizable(false);
+		dialog.setResizable(true);
 		centerDialog(dialog);
 		dialog.setVisible(true);
 		dialog.dispose();
@@ -148,24 +149,58 @@
 			return false;
 		}
 	}
+	
+	/**
+	 * Shows a security warning dialog according to the specified type of
+	 * access. If <code>type</code> is one of AccessType.VERIFIED or
+	 * AccessType.UNVERIFIED, extra details will be available with regards
+	 * to code signing and signing certificates.
+	 *
+	 * @param accessType the type of warning dialog to show
+	 * @param file the JNLPFile associated with this warning
+	 * @param jarSigner the JarSigner used to verify this application
+	 */
+	public static boolean showCertWarningDialog(AccessType accessType, 
+			JNLPFile file, JarSigner jarSigner) {
+		SecurityWarningDialog swd = 
+			new SecurityWarningDialog(DialogType.CERT_WARNING, accessType, file,
+			jarSigner);
+		JDialog dialog = swd.createDialog();
+		swd.selectInitialValue();
+		dialog.setResizable(true);
+		centerDialog(dialog);
+		dialog.setVisible(true);
+		dialog.dispose();
 
+		Object selectedValue = swd.getValue();
+		if (selectedValue == null) {
+			return false;
+		} else if (selectedValue instanceof Integer) {
+			if (((Integer)selectedValue).intValue() == 0)
+				return true;
+			else
+				return false;
+		} else {
+			return false;
+		}
+	}
+	
 	/**
 	 * Shows more information regarding jar code signing
 	 *
-	 * @param certs the certificates used in this signing
-	 * @param details the extra details regarding this signing
+	 * @param jarSigner the JarSigner used to verify this application
+	 * @param parent the parent option pane
 	 */
 	public static void showMoreInfoDialog(
-		ArrayList<CertPath> certs,
-		ArrayList<String> details, JOptionPane parent) {
+		JarSigner jarSigner, JOptionPane parent) {
 
 		SecurityWarningDialog swd =
 			new SecurityWarningDialog(DialogType.MORE_INFO, null, null,
-			certs, details);
+			jarSigner);
 		JDialog dialog = swd.createDialog();
 		dialog.setLocationRelativeTo(parent);
 		swd.selectInitialValue();
-		dialog.setResizable(false);
+		dialog.setResizable(true);
 		dialog.setVisible(true);
 		dialog.dispose();
 	}
@@ -175,10 +210,10 @@
 	 *
 	 * @param certs the certificates used in signing.
 	 */
-	public static void showCertInfoDialog(ArrayList<CertPath> certs,
+	public static void showCertInfoDialog(JarSigner jarSigner,
 		JOptionPane parent) {
 		SecurityWarningDialog swd = new SecurityWarningDialog(DialogType.CERT_INFO,
-			null, null, certs, null);
+			null, null, jarSigner);
 		JDialog dialog = swd.createDialog();
 		dialog.setLocationRelativeTo(parent);
 		swd.selectInitialValue();
@@ -189,13 +224,15 @@
 
 	//Modified from javax.swing.JOptionPane
 	private JDialog createDialog() {
-		String dialogTitle;
-		if (dialogType == DialogType.WARNING)
+		String dialogTitle = "";
+		if (dialogType == DialogType.CERT_WARNING)
 			dialogTitle = "Warning - Security";
 		else if (dialogType == DialogType.MORE_INFO)
 			dialogTitle = "More Information";
-		else
+		else if (dialogType == DialogType.CERT_INFO)
 			dialogTitle = "Details - Certificate";
+		else if (dialogType == DialogType.ACCESS_WARNING)
+			dialogTitle = "Security Warning";
 
 		final JDialog dialog = new JDialog((Frame)null, dialogTitle, true);
 		
@@ -253,13 +290,9 @@
 	public JNLPFile getFile() {
 		return file;
 	}
-
-	public ArrayList<CertPath> getCerts() {
-		return certs;
-	}
-
-	public ArrayList<String> getDetails() {
-		return details;
+	
+	public JarSigner getJarSigner() {
+		return jarSigner;
 	}
 
 	/**
@@ -268,12 +301,14 @@
 	 */
 	public void updateUI() {
 
-		if (dialogType == DialogType.WARNING)
-			setUI((OptionPaneUI) new SecurityWarningOptionPane(this));
+		if (dialogType == DialogType.CERT_WARNING)
+			setUI((OptionPaneUI) new CertWarningPane(this));
 		else if (dialogType == DialogType.MORE_INFO)
 			setUI((OptionPaneUI) new MoreInfoPane(this));
 		else if (dialogType == DialogType.CERT_INFO)
 			setUI((OptionPaneUI) new CertsInfoPane(this));
+		else if (dialogType == DialogType.ACCESS_WARNING)
+			setUI((OptionPaneUI) new AccessWarningPane(this));
 	}
 
 	private static void centerDialog(JDialog dialog) {
--- a/tools/netx/jnlp/security/SecurityWarningOptionPane.java	Mon Feb 18 10:36:18 2008 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,237 +0,0 @@
-/* SecurityWarningOptionPane.java
-   Copyright (C) 2008 Red Hat, Inc.
-
-This file is part of IcedTea.
-
-IcedTea is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License as published by
-the Free Software Foundation, version 2.
-
-IcedTea is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with IcedTea; see the file COPYING.  If not, write to
-the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301 USA.
-
-Linking this library statically or dynamically with other modules is
-making a combined work based on this library.  Thus, the terms and
-conditions of the GNU General Public License cover the whole
-combination.
-
-As a special exception, the copyright holders of this library give you
-permission to link this library with independent modules to produce an
-executable, regardless of the license terms of these independent
-modules, and to copy and distribute the resulting executable under
-terms of your choice, provided that you also meet, for each linked
-independent module, the terms and conditions of the license of that
-module.  An independent module is a module which is not derived from
-or based on this library.  If you modify this library, you may extend
-this exception to your version of the library, but you are not
-obligated to do so.  If you do not wish to do so, delete this
-exception statement from your version.
-*/
-
-package netx.jnlp.security;
-
-import java.awt.*;
-import javax.swing.*;
-import javax.swing.border.Border;
-import javax.swing.plaf.OptionPaneUI;
-import java.beans.PropertyChangeListener;
-import java.beans.PropertyChangeEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.ActionEvent;
-import java.awt.event.WindowAdapter;
-import java.awt.event.ComponentAdapter;
-import sun.swing.DefaultLookup;
-import netx.jnlp.runtime.JNLPRuntime;
-import netx.jnlp.JNLPFile;
-
-/**
- * Provides the look and feel for a SecurityWarningDialog. These dialogs are
- * used to warn the user when either signed code (with or without signing 
- * issues) is going to be run, or when service permission (file, clipboard,
- * printer, etc) is needed with unsigned code.
- *
- * @author <a href="mailto:jsumali@redhat.com">Joshua Sumali</a>
- */
-public class SecurityWarningOptionPane extends SecurityDialogUI {
-
-	public SecurityWarningOptionPane(JComponent x) {
-		super(x);
-	}
-
-	/**
-	 * Creates the actual GUI components, and adds it to <code>optionPane</code>
-	 */
-	protected void installComponents() {
-		SecurityWarningDialog.AccessType type =
-		    ((SecurityWarningDialog)optionPane).getType();
-		JNLPFile file =
-		    ((SecurityWarningDialog)optionPane).getFile();
-
-		String name = "";
-		String publisher = "";
-		String from = "";
-
-		//We don't worry about exceptions when trying to fill in
-		//these strings -- we just want to fill in as many as possible.
-		try {
-			name = file.getInformation().getTitle();
-		} catch (Exception e) {
-		}
-
-		try {
-			publisher = file.getInformation().getVendor();
-		} catch (Exception e) {
-		}
-
-		try {
-			from = file.getInformation().getHomepage().toString();
-		} catch (Exception e) {
-		}
-
-		//Used to determine whether we need another JLabel on the bottom
-		//of our dialog.
-		boolean signingRelated =
-		    (type == SecurityWarningDialog.AccessType.VERIFIED
-		     || type == SecurityWarningDialog.AccessType.UNVERIFIED);
-
-		//Top label
-		String topLabelText = "";
-		String propertyName = "";
-		switch (type) {
-		case VERIFIED:
-			topLabelText = R("SSigVerified");
-			propertyName = "OptionPane.informationIcon";
-			break;
-		case UNVERIFIED:
-			topLabelText = R("SSigUnverified");
-			propertyName = "OptionPane.warningIcon";
-			break;
-		case READ_FILE:
-			topLabelText = R("SFileReadAccess");
-			propertyName = "OptionPane.warningIcon";
-			break;
-		case WRITE_FILE:
-			topLabelText = R("SFileWriteAccess");
-			propertyName = "OptionPane.warningIcon";
-			break;
-		case CLIPBOARD_READ:
-			topLabelText = R("SClipboardReadAccess");
-			propertyName = "OptionPane.warningIcon";
-			break;
-		case CLIPBOARD_WRITE:
-			topLabelText = R("SClipboardWriteAccess");
-			propertyName = "OptionPane.warningIcon";
-			break;
-		case PRINTER:
-			topLabelText = R("SPrinterAccess");
-			propertyName = "OptionPane.warningIcon";
-			break;
-		}
-		//TODO: Get system icons and add them to our dialogs.
-		//Icon icon = (Icon)DefaultLookup.get(optionPane,this,propertyName);
-		JLabel topLabel = new JLabel(htmlWrap(topLabelText));
-		topLabel.setFont(new Font(topLabel.getFont().toString(), 
-			Font.BOLD, 12));
-		JPanel topPanel = new JPanel(new BorderLayout());
-		topPanel.setBackground(Color.WHITE);
-		topPanel.add(topLabel, BorderLayout.CENTER);
-		topPanel.setPreferredSize(new Dimension(400,60));
-		topPanel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
-
-		//application info
-		JLabel nameLabel = new JLabel("Name:   " + name);
-		nameLabel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
-		JLabel publisherLabel = new JLabel("Publisher: " + publisher);
-		publisherLabel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
-		JLabel fromLabel = new JLabel("From:   " + from);
-		fromLabel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
-
-		//TODO: enable this checkbox once we have a way to
-		//keep track of our trusted signers.
-		JCheckBox alwaysTrust;
-		if (signingRelated)   
-			alwaysTrust = new JCheckBox(
-				"Always trust content from this publisher");
-		else
-			alwaysTrust = new JCheckBox("Always allow this action.");
-		alwaysTrust.setEnabled(false);
-
-		JPanel infoPanel = new JPanel(new GridLayout(4,1));
-		infoPanel.add(nameLabel);
-		infoPanel.add(publisherLabel);
-		infoPanel.add(fromLabel);
-		infoPanel.add(alwaysTrust);
-		infoPanel.setBorder(BorderFactory.createEmptyBorder(25,25,25,25));
-
-		//run and cancel buttons
-		JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
-
-		String runButtonText = "Run";
-		if (!signingRelated)
-			runButtonText = "Allow";
-
-		JButton run = new JButton(runButtonText);
-		JButton cancel = new JButton("Cancel");
-		run.addActionListener(createButtonActionListener(0));
-		cancel.addActionListener(createButtonActionListener(1));
-		initialFocusComponent = cancel;
-		buttonPanel.add(run);
-		buttonPanel.add(cancel);
-		buttonPanel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
-
-		//all of the above
-		JPanel main = new JPanel();
-		main.setLayout(new BoxLayout(main, BoxLayout.Y_AXIS));
-		main.add(topPanel);
-		main.add(infoPanel);
-		main.add(buttonPanel);
-
-		//optional bottom panel, for when we're dealing with signing
-		//(as opposed to file/clipboard/printer access).
-		if (signingRelated) {
-
-			JLabel bottomLabel;
-			JButton moreInfo = new JButton("More information...");
-			moreInfo.addActionListener(new MoreInfoButtonListener());
-			if (type == SecurityWarningDialog.AccessType.VERIFIED)
-				bottomLabel = new JLabel(htmlWrap(R("STrustedSource")));
-			else
-				bottomLabel = new JLabel(htmlWrap(R("SUntrustedSource")));
-
-			JPanel bottomPanel = new JPanel();
-			bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.X_AXIS));
-			bottomPanel.add(bottomLabel);
-			bottomPanel.add(moreInfo);
-			bottomPanel.setPreferredSize(new Dimension(500,50));
-			bottomPanel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
-			main.add(bottomPanel);
-		}
-
-		optionPane.add(main, BorderLayout.CENTER);
-	}
-
-	private static String R(String key) {
-        return JNLPRuntime.getMessage(key);
-    }
-
-	protected String htmlWrap (String s) {
-        return "<html>"+s+"</html>";
-    }
-
-	private class MoreInfoButtonListener implements ActionListener {
-		public void actionPerformed(ActionEvent e) {
-			SecurityWarningDialog.showMoreInfoDialog(
-				((SecurityWarningDialog)optionPane).getCerts(),
-				((SecurityWarningDialog)optionPane).getDetails(), 
-				optionPane);
-		}
-	}
-
-}
--- a/tools/netx/jnlp/services/ServiceUtil.java	Mon Feb 18 10:36:18 2008 -0500
+++ b/tools/netx/jnlp/services/ServiceUtil.java	Tue Feb 19 13:34:04 2008 -0500
@@ -183,7 +183,7 @@
         ApplicationInstance app = JNLPRuntime.getApplication();
         if (app != null) {
             if (!app.isSigned()) {
-                return SecurityWarningDialog.showWarningDialog(type,
+                return SecurityWarningDialog.showAccessWarningDialog(type,
                         app.getJNLPFile());
             } else if (app.isSigned()) {
 
--- a/tools/netx/jnlp/tools/JarSigner.java	Mon Feb 18 10:36:18 2008 -0500
+++ b/tools/netx/jnlp/tools/JarSigner.java	Tue Feb 19 13:34:04 2008 -0500
@@ -42,6 +42,7 @@
 import netx.jnlp.*;
 import netx.jnlp.security.*;
 import netx.jnlp.runtime.JNLPRuntime;
+import netx.jnlp.tools.KeyTool;
 
 /**
  * <p>The jarsigner utility.
@@ -67,10 +68,6 @@
     // prefix for new signature-related files in META-INF directory
     private static final String SIG_PREFIX = META_INF + "SIG-";
 
-    private static final Class[] PARAM_STRING = { String.class };
-
-    private static final String NONE = "NONE";
-    private static final String P11KEYSTORE = "PKCS11";
 
     private static final long SIX_MONTHS = 180*24*60*60*1000L; //milliseconds
 
@@ -115,14 +112,6 @@
     boolean signManifest = true; // "sign" the whole manifest
     boolean externalSF = true; // leave the .SF out of the PKCS7 block
 
-    // read zip entry raw bytes
-    private ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
-    private byte[] buffer = new byte[8192];
-//   private ContentSigner signingMechanism = null;
-    private String altSignerClass = null;
-    private String altSignerClasspath = null;
-    private ZipFile zipFile = null;
-    private boolean hasUnsignedEntry = false;
     private boolean hasExpiredCert = false;
     private boolean hasExpiringCert = false;
     private boolean notYetValidCert = false;
@@ -131,7 +120,17 @@
     private boolean badExtendedKeyUsage = false;
     private boolean badNetscapeCertType = false;
 
-    private boolean allVerified = true;
+    private boolean alreadyTrustPublisher = false;
+    private boolean rootInCacerts = false;
+    
+    /**
+     * The single certPath used in this JarSiging. We're only keeping
+     * track of one here, since in practice there's only one signer
+     * for a JNLP Application.
+     */
+    private CertPath certPath = null;
+    
+    private boolean noSigningIssues = true;
 
     private boolean anyJarsSigned = false;
 
@@ -147,13 +146,25 @@
     /** details of this signing */
     private ArrayList<String> details = new ArrayList<String>();
 
+    public boolean getAlreadyTrustPublisher() {
+    	return alreadyTrustPublisher;
+    }
+    
+    public boolean getRootInCacerts() {
+    	return rootInCacerts;
+    }
+    
+    public CertPath getCertPath() {
+    	return certPath;
+    }
+    
     public boolean hasSigningIssues() {
         return hasExpiredCert || notYetValidCert || badKeyUsage
                || badExtendedKeyUsage || badNetscapeCertType;
     }
 
-    public boolean allVerified() {
-        return allVerified;
+    public boolean noSigningIssues() {
+        return noSigningIssues;
     }
 
     public boolean anyJarsSigned() {
@@ -181,18 +192,19 @@
             try {
                 String localFile = tracker.getCacheFile(jar.getLocation()).getAbsolutePath();
                 boolean result = verifyJar(localFile);
+                checkTrustedCerts();
 
                 if (!result) {
                     //allVerified is true until we encounter a problem
                     //with one or more jars
-                    allVerified = false;
+                    noSigningIssues = false;
                     unverifiedJars.add(localFile);
                 } else {
                     verifiedJars.add(localFile);
                 }
             } catch (Exception e){
-                //We may catch exceptions from using js.verifyJar(localFile).
-                e.printStackTrace();
+                // We may catch exceptions from using verifyJar()
+            	// or from checkTrustedCerts	
                 throw e;
             }
         }
@@ -244,10 +256,19 @@
                     hasUnsignedEntry |= !je.isDirectory() && !isSigned
                                         && !signatureRelated(name);
                     if (isSigned) {
+                    	// TODO: Perhaps we should check here that
+                    	// signers.length is only of size 1, and throw an
+                    	// exception if it's not?
                         for (int i = 0; i < signers.length; i++) {
                             CertPath certPath = signers[i].getSignerCertPath();
                             if (!certs.contains(certPath))
                                 certs.add(certPath);
+                            
+                            //we really only want the first certPath
+                            if (!certPath.equals(this.certPath)){
+                            	this.certPath = certPath;
+                            }
+                            
                             Certificate cert = signers[i].getSignerCertPath()
                                 .getCertificates().get(0);
                             if (cert instanceof X509Certificate) {
@@ -313,19 +334,72 @@
                               || notYetValidCert);
     }
 
+    /**
+     * Checks the user's trusted.certs file and the cacerts file to see
+     * if a publisher's and/or CA's certificate exists there.
+     */
+    private void checkTrustedCerts() throws Exception {
+    	if (certPath != null) {
+    		try {
+    			KeyTool kt = new KeyTool();
+    			alreadyTrustPublisher = kt.isTrusted(getPublisher());
+   				rootInCacerts = kt.checkCacertsForCertificate(getRoot());
+    		} catch (Exception e) {
+    			// TODO: Warn user about not being able to
+    			// look through their cacerts/trusted.certs
+    			// file depending on exception.
+    			throw e;
+    		}
+    		
+    		if (!rootInCacerts)
+    			addToDetails(R("SUntrustedCertificate"));
+    		else 
+    			addToDetails(R("STrustedCertificate"));
+    	}
+    }
+    
+    /** 
+     * Returns the application's publisher's certificate.
+     */
+    public Certificate getPublisher() {
+    	if (certPath != null) {
+    		List<? extends Certificate> certList 
+			= certPath.getCertificates();
+    		if (certList.size() > 0) {
+    			return (Certificate)certList.get(0);
+    		} else {
+    			return null;
+    		}
+    	} else {
+    		return null;
+    	}
+    }
+    
+    /**
+     * Returns the application's root's certificate. This
+     * may return the same certificate as getPublisher() in
+     * the event that the application is self signed.
+     */
+    public Certificate getRoot() {
+    	if (certPath != null) {
+    		List<? extends Certificate> certList 
+			= certPath.getCertificates();
+    		if (certList.size() > 0) {
+    			return (Certificate)certList.get(
+    				certList.size() - 1);
+    		} else {
+    			return null;
+    		}
+    	} else {
+    		return null;
+    	}
+    }
+    
 	private void addToDetails(String detail) {
 		if (!details.contains(detail))
 			details.add(detail);
 	}
 
-    private static MessageFormat validityTimeForm = null;
-    private static MessageFormat notYetTimeForm = null;
-    private static MessageFormat expiredTimeForm = null;
-    private static MessageFormat expiringTimeForm = null;
-
-    private static MessageFormat signTimeForm = null;
-
-
     Hashtable<Certificate, String> storeHash =
         new Hashtable<Certificate, String>();
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/netx/jnlp/tools/KeyTool.java	Tue Feb 19 13:34:04 2008 -0500
@@ -0,0 +1,537 @@
+/*
+ * Copyright 1997-2006 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package netx.jnlp.tools;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+import java.security.KeyStore;
+import java.security.MessageDigest;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * This tool manages the user's trusted certificates
+ *
+ * @author Jan Luehe
+ * @author Joshua Sumali
+ */
+public class KeyTool {
+
+	// The user's keystore.
+	private KeyStore usercerts = null;
+	// JDK cacerts
+	private KeyStore cacerts = null;
+	// System ca-bundle.crt
+	private KeyStore systemcerts = null;
+	private String homeDir = null;
+	private final String certPath = "/.netx/security/";
+	private final String certFile = "trusted.certs";
+	private String fullCertPath = null;
+	//private CertificateFactory cf = null;
+
+	private FileInputStream fis = null;
+	private FileOutputStream fos = null;
+
+	/**
+	 * Whether we trust the system cacerts file.
+	 */
+	private boolean trustcacerts = true;
+	
+	private final char[] password = "changeit".toCharArray();
+
+	/**
+	 * Whether we prompt for user input.
+	 */
+	private boolean noprompt = true;
+	
+	public KeyTool() throws Exception {
+
+		// Initialize all the keystores.
+		usercerts = getUserKeyStore();
+		cacerts = getCacertsKeyStore(); 
+		systemcerts = getSystemCertStore();
+	}
+
+	/**
+	 * Add's a trusted certificate to the user's keystore.
+	 * @return true if the add was successful, false otherwise.
+	 */
+	public boolean importCert(Certificate cert) throws Exception {
+
+		String alias = usercerts.getCertificateAlias(cert);
+
+		if (alias != null) { //cert already exists
+			return true;
+		} else {
+			String newAlias = getRandomAlias();
+			//check to make sure this alias doesn't exist
+			while (usercerts.getCertificate(newAlias) != null)
+				newAlias = getRandomAlias();
+			return addTrustedCert(newAlias, cert);
+		}
+	}
+
+	/**
+	 * Generates a random alias for storing a trusted Certificate.
+	 */
+	private String getRandomAlias() {
+		Random r = new Random();
+		String token = Long.toString(Math.abs(r.nextLong()), 36);
+		return "trustedCert-" + token;
+	}
+	
+	/**
+	 * Checks the user's home directory to see if the trusted.certs file exists.
+	 * If it does not exist, it tries to create an empty keystore.
+	 * @return true if the trusted.certs file exists or a new trusted.certs
+	 * was created successfully, otherwise false.
+	 */
+	private boolean checkFiles() throws Exception {
+
+		File certFile = new File(fullCertPath);
+
+		if (!certFile.isFile()) { //file does not exist
+			File certDir = new File(homeDir+certPath);
+			boolean madeDir = false;
+			if (!certDir.isDirectory()) { //directory does not exist
+				madeDir = (new File(homeDir+certPath)).mkdirs();
+			}
+			
+			if (madeDir) {
+				usercerts.load(null, password);
+				fos = new FileOutputStream(certFile);
+				usercerts.store(fos,	password);
+				fos.close();
+				return true;
+			} else {
+				return false;
+			}
+		} else { //cert file already exists
+			return true;
+		}
+	}
+	
+	/**
+     * Prints all keystore entries.
+     */
+	private void doPrintEntries(PrintStream out) throws Exception {
+
+		out.println("KeyStore type: " + usercerts.getType());
+		out.println("KeyStore provider: " + usercerts.getProvider().toString());
+		out.println();
+		
+		for (Enumeration<String> e = usercerts.aliases(); e.hasMoreElements();) {
+			String alias = e.nextElement();
+			doPrintEntry(alias, out, false);
+		}
+	}
+	
+    /**
+     * Prints a single keystore entry.
+     */
+	private void doPrintEntry(String alias, PrintStream out,
+			boolean printWarning) throws Exception {
+
+		if (usercerts.containsAlias(alias) == false) {
+			throw new Exception("Alias does not exist");
+		}
+
+		if (usercerts.entryInstanceOf(alias, 
+				KeyStore.TrustedCertificateEntry.class)) {
+			Certificate cert = usercerts.getCertificate(alias);
+
+			out.println("Alias: " + alias);
+			out.println("Date Created: " + usercerts.getCreationDate(alias));
+			out.println("Subject: " + getCN(((X509Certificate)usercerts
+				.getCertificate(alias)).getSubjectX500Principal().getName()));
+			out.println("Certificate fingerprint (MD5): "
+					+ getCertFingerPrint("MD5", cert));
+			out.println();
+		}
+	}
+
+	/**
+	 * Extracts the CN field from a Certificate principal string.
+	 */
+	private String getCN(String principal) {
+		int start = principal.indexOf("CN=");
+		int end = principal.indexOf(",", start);
+
+		if (end == -1) {
+			end = principal.length();
+		}
+
+		if (start >= 0)
+			return principal.substring(start+3, end);
+		else
+			return principal;
+    }
+
+    /**
+     * Gets the requested finger print of the certificate.
+     */
+	private String getCertFingerPrint(String mdAlg, Certificate cert)
+		throws Exception {
+		byte[] encCertInfo = cert.getEncoded();
+		MessageDigest md = MessageDigest.getInstance(mdAlg);
+		byte[] digest = md.digest(encCertInfo);
+		return toHexString(digest);
+	}
+
+    /**
+     * Converts a byte to hex digit and writes to the supplied buffer
+     */
+    private void byte2hex(byte b, StringBuffer buf) {
+        char[] hexChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
+                            '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+        int high = ((b & 0xf0) >> 4);
+        int low = (b & 0x0f);
+        buf.append(hexChars[high]);
+        buf.append(hexChars[low]);
+    }
+
+    /**
+     * Converts a byte array to hex string
+     */
+    private String toHexString(byte[] block) {
+        StringBuffer buf = new StringBuffer();
+        int len = block.length;
+        for (int i = 0; i < len; i++) {
+             byte2hex(block[i], buf);
+             if (i < len-1) {
+                 buf.append(":");
+             }
+        }
+        return buf.toString();
+    }
+
+	/**
+	 * Adds a certificate to the keystore, and writes new keystore to disk.
+	 */
+    private boolean addTrustedCert(String alias, Certificate cert)
+    	throws Exception {
+    	
+    	if (isSelfSigned((X509Certificate)cert)) {
+			//will throw exception if this fails
+    		cert.verify(cert.getPublicKey());
+		}
+    
+    	if (noprompt) {
+    		usercerts.setCertificateEntry(alias, cert);
+			fos = new FileOutputStream(fullCertPath);
+			usercerts.store(fos, password);
+			fos.close();
+    		return true;
+    	}
+    	
+    	return false;
+    }    
+    
+    /**
+     * Returns true if the given certificate is trusted, false otherwise.
+     */
+    public boolean isTrusted(Certificate cert) throws Exception {
+    	if (cert != null) {
+    		if (usercerts.getCertificateAlias(cert) != null) {
+    			return true; // found in own keystore
+    		}
+    		return false;
+    	} else {
+    		return false;
+    	}
+    }
+    
+    /**
+     * Returns true if the certificate is self-signed, false otherwise.
+     */
+    private boolean isSelfSigned(X509Certificate cert) {
+        return cert.getSubjectDN().equals(cert.getIssuerDN());
+    }
+
+	/**
+	 * Returns the keystore associated with the users
+	 * trusted.certs file, or null otherwise.
+	 */
+	private KeyStore getUserKeyStore() throws Exception {
+
+		homeDir = System.getProperty("user.home");
+		fullCertPath = homeDir + certPath + certFile;
+		KeyStore ks = KeyStore.getInstance("JKS");
+
+		if (ks != null && checkFiles()) {
+			fis = new FileInputStream(fullCertPath);
+			ks.load(fis, password);
+			if (fis != null)
+				fis.close();
+		}
+		return ks;
+	}
+
+    /**
+     * Returns the keystore associated with the JDK cacerts file, 
+	 * or null otherwise.
+     */
+    private KeyStore getCacertsKeyStore() throws Exception {
+
+		KeyStore caks = null;
+		FileInputStream fis = null;
+
+		try {
+        	String sep = File.separator;
+        	File file = new File(System.getProperty("java.home") + sep
+							+ "lib" + sep + "security" + sep
+							+ "cacerts");
+        	caks = null;
+            fis = new FileInputStream(file);
+            caks = KeyStore.getInstance("JKS");
+            caks.load(fis, null);
+		} catch (Exception e) {
+			caks = null;
+		} finally {
+			if (fis != null)
+				fis.close();
+		}
+
+		return caks;
+    }
+
+	/**
+	 * Returns the keystore associated with the system certs file,
+	 * or null otherwise.
+	 */
+	private KeyStore getSystemCertStore() throws Exception {
+
+		KeyStore caks = null;
+		FileInputStream fis = null;
+
+		try {
+			String file = System.getProperty("javax.net.ssl.trustStore");
+			String type = System.getProperty("javax.net.ssl.trustStoreType");
+			String provider = "SUN";
+			char[] password = System.getProperty(
+				"javax.net.ssl.trustStorePassword").toCharArray();
+        	caks = KeyStore.getInstance(type);
+        	fis = new FileInputStream(file);
+        	caks.load(fis, password);
+		} catch (Exception e) {
+			caks = null;
+		} finally {
+			if (fis != null)
+				fis.close();
+		}
+		
+		return caks;
+	}
+
+    /**
+     * Checks if a given certificate is part of the user's cacerts
+     * keystore.
+     * @param c the certificate to check
+     * @returns true if the certificate is in the user's cacerts and
+     * false otherwise
+     */
+    public boolean checkCacertsForCertificate(Certificate c) throws Exception {
+    	if (c != null) {
+
+			String alias = null;
+
+			//first try jdk cacerts.
+			if (cacerts != null) {
+    			alias = cacerts.getCertificateAlias(c);
+
+				//if we can't find it here, try the system certs.
+				if (alias == null && systemcerts != null)
+					alias = systemcerts.getCertificateAlias(c);
+			} 
+			//otherwise try the system certs if you can't use the jdk certs.
+			else if (systemcerts != null)
+				alias = systemcerts.getCertificateAlias(c);
+
+    		return (alias != null);
+    	} else 
+    		return false;
+    }
+    
+    /**
+     * Establishes a certificate chain (using trusted certificates in the
+     * keystore), starting with the user certificate
+     * and ending at a self-signed certificate found in the keystore.
+     *
+     * @param userCert the user certificate of the alias
+     * @param certToVerify the single certificate provided in the reply
+     */
+    public boolean establishCertChain(Certificate userCert,
+                                             Certificate certToVerify)
+        throws Exception
+    {
+        if (userCert != null) {
+            // Make sure that the public key of the certificate reply matches
+            // the original public key in the keystore
+            PublicKey origPubKey = userCert.getPublicKey();
+            PublicKey replyPubKey = certToVerify.getPublicKey();
+            if (!origPubKey.equals(replyPubKey)) {
+            	// TODO: something went wrong -- throw exception
+                throw new Exception(
+                	"Public keys in reply and keystore don't match");
+            }
+
+            // If the two certs are identical, we're done: no need to import
+            // anything
+            if (certToVerify.equals(userCert)) {
+                throw new Exception(
+                	"Certificate reply and certificate in keystore are identical");
+            }
+        }
+
+        // Build a hash table of all certificates in the keystore.
+        // Use the subject distinguished name as the key into the hash table.
+        // All certificates associated with the same subject distinguished
+        // name are stored in the same hash table entry as a vector.
+        Hashtable<Principal, Vector<Certificate>> certs = null;
+        if (usercerts.size() > 0) {
+            certs = new Hashtable<Principal, Vector<Certificate>>(11);
+            keystorecerts2Hashtable(usercerts, certs);
+        }
+        if (trustcacerts) { //if we're trusting the cacerts
+        	KeyStore caks = getCacertsKeyStore();
+            if (caks!=null && caks.size()>0) {
+                if (certs == null) {
+                    certs = new Hashtable<Principal, Vector<Certificate>>(11);
+                }
+                keystorecerts2Hashtable(caks, certs);
+            }
+        }
+
+        // start building chain
+        Vector<Certificate> chain = new Vector<Certificate>(2);
+        if (buildChain((X509Certificate)certToVerify, chain, certs)) {
+            Certificate[] newChain = new Certificate[chain.size()];
+            // buildChain() returns chain with self-signed root-cert first and
+            // user-cert last, so we need to invert the chain before we store
+            // it
+            int j=0;
+            for (int i=chain.size()-1; i>=0; i--) {
+                newChain[j] = chain.elementAt(i);
+                j++;
+            }
+            //return newChain;
+            System.out.println("newChain's size: " + newChain.length);
+            return newChain != null;
+        } else {
+            throw new Exception("Failed to establish chain from reply");
+        }
+    }
+    
+    /**
+     * Stores the (leaf) certificates of a keystore in a hashtable.
+     * All certs belonging to the same CA are stored in a vector that
+     * in turn is stored in the hashtable, keyed by the CA's subject DN
+     */
+    private void keystorecerts2Hashtable(KeyStore ks,
+                Hashtable<Principal, Vector<Certificate>> hash)
+        throws Exception {
+
+        for (Enumeration<String> aliases = ks.aliases();
+                                        aliases.hasMoreElements(); ) {
+            String alias = aliases.nextElement();
+            Certificate cert = ks.getCertificate(alias);
+            if (cert != null) {
+                Principal subjectDN = ((X509Certificate)cert).getSubjectDN();
+                Vector<Certificate> vec = hash.get(subjectDN);
+                if (vec == null) {
+                    vec = new Vector<Certificate>();
+                    vec.addElement(cert);
+                } else {
+                    if (!vec.contains(cert)) {
+                        vec.addElement(cert);
+                    }
+                }
+                hash.put(subjectDN, vec);
+            }
+        }
+    }
+    
+    /**
+     * Recursively tries to establish chain from pool of trusted certs.
+     *
+     * @param certToVerify the cert that needs to be verified.
+     * @param chain the chain that's being built.
+     * @param certs the pool of trusted certs
+     *
+     * @return true if successful, false otherwise.
+     */
+    private boolean buildChain(X509Certificate certToVerify,
+                        Vector<Certificate> chain,
+                        Hashtable<Principal, Vector<Certificate>> certs) {
+        Principal subject = certToVerify.getSubjectDN();
+        Principal issuer = certToVerify.getIssuerDN();
+        if (subject.equals(issuer)) {
+            // reached self-signed root cert;
+            // no verification needed because it's trusted.
+            chain.addElement(certToVerify);
+            return true;
+        }
+
+        // Get the issuer's certificate(s)
+        Vector<Certificate> vec = certs.get(issuer);
+        if (vec == null) {
+            return false;
+        }
+
+        // Try out each certificate in the vector, until we find one
+        // whose public key verifies the signature of the certificate
+        // in question.
+        for (Enumeration<Certificate> issuerCerts = vec.elements();
+             issuerCerts.hasMoreElements(); ) {
+            X509Certificate issuerCert
+                = (X509Certificate)issuerCerts.nextElement();
+            PublicKey issuerPubKey = issuerCert.getPublicKey();
+            try {
+                certToVerify.verify(issuerPubKey);
+            } catch (Exception e) {
+                continue;
+            }
+            if (buildChain(issuerCert, chain, certs)) {
+                chain.addElement(certToVerify);
+                return true;
+            }
+        }
+        return false;
+    }
+
+	public static void main(String[] args) throws Exception {
+		KeyTool kt = new KeyTool();
+		kt.doPrintEntries(System.out);
+	}
+}