Mercurial > hg > release > icedtea6-1.6
changeset 720: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); + } +}