diff options
author | Adam Domurad <[email protected]> | 2013-03-26 14:57:33 -0400 |
---|---|---|
committer | Adam Domurad <[email protected]> | 2013-03-26 14:57:33 -0400 |
commit | 58b4d6e3dc3a95cfbc6f369287aca04763522e48 (patch) | |
tree | c31e0146381288222138a667844893106772d87e | |
parent | c4f2dc4c8dc8c025c5d27c627717a164755986ae (diff) |
Integration of unsigned applet confirmation dialogue.
12 files changed, 668 insertions, 10 deletions
@@ -1,3 +1,35 @@ +2013-03-26 Adam Domurad <[email protected]> + + Integration of unsigned applet confirmation dialogue. + * netx/net/sourceforge/jnlp/PluginBridge.java + (getArchiveJars): New, returns archive jars as list + * netx/net/sourceforge/jnlp/resources/Messages.properties: + Confirmation messages added to properties file + * netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java + (getInstance): Initialization refactored into createInstance + (createInstance): New, checks if unsigned applet is allowed, + initializes classloader. + (initializeResources): Don't consider no-jar applets signed. + * netx/net/sourceforge/jnlp/security/SecurityDialogs.java + (showUnsignedWarningDialog): Creates message with + DialogType.UNSIGNED_WARNING + * netx/net/sourceforge/jnlp/security/SecurityDialog.java + (installPanel): Add case for DialogType.UNSIGNED_WARNING + * netx/net/sourceforge/jnlp/security/appletextendedsecurity/UnsignedAppletActionStorage.java: + Expose locking members from interface + * plugin/icedteanp/java/sun/applet/PluginAppletViewer.java + (handleInitializationMessage): Do nothing if applets have been + disabled. + * netx/net/sourceforge/jnlp/security/UnsignedAppletTrustWarningDialog.java: + New, security dialog that asks for unsigned applet confirmation. + * netx/net/sourceforge/jnlp/security/UnsignedAppletTrustWarningPanel.java: + Implements the dialog contents for unsigned applet confirmation. + * netx/net/sourceforge/jnlp/security/appletextendedsecurity/UnsignedAppletTrustConfirmation.java: + Updates and checks applet confirmation storage, creates warning dialog + if required. + * tests/netx/unit/net/sourceforge/jnlp/security/appletextendedsecurity/UnsignedAppletTrustConfirmationTest.java: + New, tests relative & normalized path creation helpers. + 2013-03-26 Jiri Vanek <[email protected]> Path validator fixed to be correctly multiplatform diff --git a/netx/net/sourceforge/jnlp/PluginBridge.java b/netx/net/sourceforge/jnlp/PluginBridge.java index 1e34c49..98dee8e 100644 --- a/netx/net/sourceforge/jnlp/PluginBridge.java +++ b/netx/net/sourceforge/jnlp/PluginBridge.java @@ -206,6 +206,10 @@ public class PluginBridge extends JNLPFile { } } + public List<String> getArchiveJars() { + return new ArrayList<String>(jars); + } + public boolean codeBaseLookup() { return params.useCodebaseLookup(); } diff --git a/netx/net/sourceforge/jnlp/resources/Messages.properties b/netx/net/sourceforge/jnlp/resources/Messages.properties index 105b09e..7d66087 100644 --- a/netx/net/sourceforge/jnlp/resources/Messages.properties +++ b/netx/net/sourceforge/jnlp/resources/Messages.properties @@ -80,6 +80,9 @@ LUnsignedJarWithSecurity=Cannot grant permissions to unsigned jars. LUnsignedJarWithSecurityInfo=Application requested security permissions, but jars are not signed. LSignedJNLPAppDifferentCerts=The JNLP application is not fully signed by a single cert. LSignedJNLPAppDifferentCertsInfo=The JNLP application has its components individually signed, however there must be a common signer to all entries. +LUnsignedApplet=The applet was unsigned. +LUnsignedAppletPolicyDenied=The applet was unsigned, and the security policy prevented it from running. +LUnsignedAppletUserDenied=The applet was unsigned, and was not trusted. LSignedAppJarUsingUnsignedJar=Signed application using unsigned jars. LSignedAppJarUsingUnsignedJarInfo=The main application jar is signed, but some of the jars it is using aren't. LSignedJNLPFileDidNotMatch=The signed JNLP file did not match the launching JNLP file. @@ -220,6 +223,12 @@ SNoAssociatedCertificate=<no associated certificate> SUnverified=(unverified) SAlwaysTrustPublisher=Always trust content from this publisher SHttpsUnverified=The website's HTTPS certificate cannot be verified. +SRememberOption=<b>Remember this option?</b> +SUnsignedSummary=An unsigned Java application wants to run +SUnsignedDetail=An unsigned application from the following location wants to run:<br><u>{0}</u><br><br><b>It is recommended you only run applications from sites you trust.</b> +SUnsignedAllowedBefore=<font color="green">You have accepted this applet previously.</font> +SUnsignedRejectedBefore=<font color="red">You have rejected this applet previously.</font> +SUnsignedQuestion=Allow the applet to run? SNotAllSignedSummary=Only parts of this application code are signed. SNotAllSignedDetail=This application contains both signed and unsigned code. While signed code is safe if you trust the provider, unsigned code may imply code outside of the trusted provider's control. SNotAllSignedQuestion=Do you wish to proceed and run this application anyway? diff --git a/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java b/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java index 484d4f4..983979e 100644 --- a/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java +++ b/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java @@ -63,6 +63,7 @@ import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; +import net.sourceforge.jnlp.security.appletextendedsecurity.UnsignedAppletTrustConfirmation; import net.sourceforge.jnlp.AppletDesc; import net.sourceforge.jnlp.ApplicationDesc; import net.sourceforge.jnlp.DownloadOptions; @@ -380,6 +381,13 @@ public class JNLPClassLoader extends URLClassLoader { JNLPClassLoader baseLoader = uniqueKeyToLoader.get(uniqueKey); JNLPClassLoader loader = new JNLPClassLoader(file, policy, mainName); + // If security level is 'high' or greater, we must check if the user allows unsigned applets + // when the JNLPClassLoader is created. We do so here, because doing so in the constructor + // causes unwanted side-effects for some applets + if (!loader.getSigning() && file instanceof PluginBridge) { + UnsignedAppletTrustConfirmation.checkUnsignedWithUserIfRequired((PluginBridge)file); + } + // New loader init may have caused extentions to create a // loader for this unique key. Check. JNLPClassLoader extLoader = uniqueKeyToLoader.get(uniqueKey); @@ -596,9 +604,9 @@ public class JNLPClassLoader extends URLClassLoader { JARDesc jars[] = resources.getJARs(); - if (jars == null || jars.length == 0) { + if (jars.length == 0) { - boolean allSigned = true; + boolean allSigned = (loaders.length > 1) /* has extensions */; for (int i = 1; i < loaders.length; i++) { if (!loaders[i].getSigning()) { allSigned = false; @@ -681,7 +689,6 @@ public class JNLPClassLoader extends URLClassLoader { !SecurityDialogs.showNotAllSignedWarningDialog(file)) throw new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LSignedAppJarUsingUnsignedJar"), R("LSignedAppJarUsingUnsignedJarInfo")); - // Check for main class in the downloaded jars, and check/verify signed JNLP fill checkForMain(initialJars); @@ -718,9 +725,9 @@ public class JNLPClassLoader extends URLClassLoader { } } else { + // Otherwise this jar is simply unsigned -- make sure to ask + // for permission on certain actions signing = false; - //otherwise this jar is simply unsigned -- make sure to ask - //for permission on certain actions } } diff --git a/netx/net/sourceforge/jnlp/security/SecurityDialog.java b/netx/net/sourceforge/jnlp/security/SecurityDialog.java index 6420c29..e204f0e 100644 --- a/netx/net/sourceforge/jnlp/security/SecurityDialog.java +++ b/netx/net/sourceforge/jnlp/security/SecurityDialog.java @@ -38,6 +38,7 @@ exception statement from your version. package net.sourceforge.jnlp.security; import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.PluginBridge; import net.sourceforge.jnlp.runtime.JNLPRuntime; import net.sourceforge.jnlp.security.SecurityDialogs.AccessType; import net.sourceforge.jnlp.security.SecurityDialogs.DialogType; @@ -306,6 +307,8 @@ public class SecurityDialog extends JDialog { panel = new AppletWarningPane(this, this.certVerifier); else if (dialogType == DialogType.NOTALLSIGNED_WARNING) panel = new NotAllSignedWarningPane(this); + else if (dialogType == DialogType.UNSIGNED_WARNING) // Only necessary for applets on 'high security' or above + panel = new UnsignedAppletTrustWarningDialog(this, (PluginBridge)file); else if (dialogType == DialogType.AUTHENTICATION) panel = new PasswordAuthenticationPane(this, extras); diff --git a/netx/net/sourceforge/jnlp/security/SecurityDialogs.java b/netx/net/sourceforge/jnlp/security/SecurityDialogs.java index c5bf824..a1af82e 100644 --- a/netx/net/sourceforge/jnlp/security/SecurityDialogs.java +++ b/netx/net/sourceforge/jnlp/security/SecurityDialogs.java @@ -37,6 +37,8 @@ exception statement from your version. package net.sourceforge.jnlp.security; +import net.sourceforge.jnlp.security.appletextendedsecurity.ExecuteUnsignedApplet; + import java.awt.Dialog.ModalityType; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; @@ -70,6 +72,7 @@ public class SecurityDialogs { SINGLE_CERT_INFO, ACCESS_WARNING, NOTALLSIGNED_WARNING, + UNSIGNED_WARNING, /* requires confirmation with 'high-security' setting */ APPLET_WARNING, AUTHENTICATION, } @@ -86,6 +89,7 @@ public class SecurityDialogs { VERIFIED, UNVERIFIED, NOTALLSIGNED, + UNSIGNED, /* requires confirmation with 'high-security' setting */ SIGNING_ERROR } @@ -173,6 +177,26 @@ public class SecurityDialogs { } /** + * Shows a warning dialog for when a plugin applet is unsigned. + * This is used with 'high-security' setting. + * + * @return true if permission was granted by the user, false otherwise. + */ + public static ExecuteUnsignedApplet showUnsignedWarningDialog(JNLPFile file) { + + if (!shouldPromptUser()) { + return ExecuteUnsignedApplet.NO; + } + + final SecurityDialogMessage message = new SecurityDialogMessage(); + message.dialogType = DialogType.UNSIGNED_WARNING; + message.accessType = AccessType.UNSIGNED; + message.file = file; + + return (ExecuteUnsignedApplet)getUserResponse(message); + } + + /** * 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 diff --git a/netx/net/sourceforge/jnlp/security/UnsignedAppletTrustWarningDialog.java b/netx/net/sourceforge/jnlp/security/UnsignedAppletTrustWarningDialog.java new file mode 100644 index 0000000..4860e52 --- /dev/null +++ b/netx/net/sourceforge/jnlp/security/UnsignedAppletTrustWarningDialog.java @@ -0,0 +1,63 @@ +/* Copyright (C) 2013 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 net.sourceforge.jnlp.security; + +import net.sourceforge.jnlp.PluginBridge; +import net.sourceforge.jnlp.security.UnsignedAppletTrustWarningPanel.ActionChoiceListener; +import net.sourceforge.jnlp.security.appletextendedsecurity.ExecuteUnsignedApplet; + +/** + * A panel that confirms that the user is OK with unsigned code running. + * + */ +public class UnsignedAppletTrustWarningDialog extends SecurityDialogPanel { + + public UnsignedAppletTrustWarningDialog(SecurityDialog x, PluginBridge file) { + super(x); + + add(new UnsignedAppletTrustWarningPanel(file, + new ActionChoiceListener() { + @Override + public void actionChosen(ExecuteUnsignedApplet action) { + parent.setValue(action); + parent.dispose(); + } + }) + ); + } + +} diff --git a/netx/net/sourceforge/jnlp/security/UnsignedAppletTrustWarningPanel.java b/netx/net/sourceforge/jnlp/security/UnsignedAppletTrustWarningPanel.java new file mode 100644 index 0000000..e20967c --- /dev/null +++ b/netx/net/sourceforge/jnlp/security/UnsignedAppletTrustWarningPanel.java @@ -0,0 +1,228 @@ +/* Copyright (C) 2013 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 net.sourceforge.jnlp.security; + +import static net.sourceforge.jnlp.runtime.Translator.R; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingConstants; + +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.PluginBridge; +import net.sourceforge.jnlp.security.appletextendedsecurity.ExecuteUnsignedApplet; +import net.sourceforge.jnlp.security.appletextendedsecurity.UnsignedAppletTrustConfirmation; + +public class UnsignedAppletTrustWarningPanel extends JPanel { + + /* + * Callback for when action is decided. + */ + public static interface ActionChoiceListener { + void actionChosen(ExecuteUnsignedApplet action); + } + + private final int PANE_WIDTH = 500; + + private final int TOP_PANEL_HEIGHT = 60; + private final int INFO_PANEL_HEIGHT = 100; + private final int INFO_PANEL_HINT_HEIGHT = 25; + private final int QUESTION_PANEL_HEIGHT = 35; + + private JButton allowButton; + private JButton rejectButton; + private JCheckBox permanencyCheckBox; + + private PluginBridge file; + + private ActionChoiceListener actionChoiceListener; + + public UnsignedAppletTrustWarningPanel(PluginBridge file, ActionChoiceListener actionChoiceListener) { + + this.file = file; + this.actionChoiceListener = actionChoiceListener; + + addComponents(); + } + + public JButton getAllowButton() { + return allowButton; + } + + public JButton getRejectButton() { + return rejectButton; + } + + private String htmlWrap(String text) { + return "<html>" + text + "</html>"; + } + + private ImageIcon infoImage() { + final String location = "net/sourceforge/jnlp/resources/info-small.png"; + final ClassLoader appLoader = new sun.misc.Launcher().getClassLoader(); + return new ImageIcon(appLoader.getResource(location)); + } + + private void setupTopPanel() { + final String topLabelText = R("SUnsignedSummary"); + + JLabel topLabel = new JLabel(htmlWrap(topLabelText), infoImage(), + SwingConstants.LEFT); + 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(PANE_WIDTH, TOP_PANEL_HEIGHT)); + topPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + add(topPanel); + } + + private void setupInfoPanel() { + String infoLabelText = R("SUnsignedDetail", file.getCodeBase()); + ExecuteUnsignedApplet rememberedAction = UnsignedAppletTrustConfirmation.getStoredAction((PluginBridge)file); + int panelHeight = INFO_PANEL_HEIGHT; + if (rememberedAction == ExecuteUnsignedApplet.YES) { + infoLabelText += "<br>" + R("SUnsignedAllowedBefore"); + panelHeight += INFO_PANEL_HINT_HEIGHT; + } else if (rememberedAction == ExecuteUnsignedApplet.NO) { + infoLabelText += "<br>" + R("SUnsignedRejectedBefore"); + panelHeight += INFO_PANEL_HINT_HEIGHT; + } + + JLabel infoLabel = new JLabel(htmlWrap(infoLabelText)); + JPanel infoPanel = new JPanel(new BorderLayout()); + infoPanel.add(infoLabel, BorderLayout.CENTER); + infoPanel.setPreferredSize(new Dimension(PANE_WIDTH, panelHeight)); + infoPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + add(infoPanel); + } + + private void setupQuestionsPanel() { + JPanel questionPanel = new JPanel(new BorderLayout()); + + questionPanel.add(new JLabel(htmlWrap(R("SUnsignedQuestion"))), BorderLayout.EAST); + + questionPanel.setPreferredSize(new Dimension(PANE_WIDTH, QUESTION_PANEL_HEIGHT)); + questionPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + add(questionPanel); + } + + private JPanel createCheckBoxPanel() { + JPanel checkBoxPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + + permanencyCheckBox = new JCheckBox(htmlWrap(R("SRememberOption"))); + checkBoxPanel.add(permanencyCheckBox); + + checkBoxPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + return checkBoxPanel; + } + + private JPanel createButtonPanel() { + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + + allowButton = new JButton(R("ButProceed")); + rejectButton = new JButton(R("ButCancel")); + + allowButton.addActionListener(chosenActionSetter(true)); + rejectButton.addActionListener(chosenActionSetter(false)); + + buttonPanel.add(allowButton); + buttonPanel.add(rejectButton); + + buttonPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + return buttonPanel; + } + + // Set up 'Remember Option' checkbox & Proceed/Cancel buttons + private void setupButtonAndCheckBoxPanel() { + JPanel outerPanel = new JPanel(new BorderLayout()); + + outerPanel.add(createCheckBoxPanel(), BorderLayout.WEST); + outerPanel.add(createButtonPanel(), BorderLayout.EAST); + + add(outerPanel); + } + + /** + * Creates the actual GUI components, and adds it to this panel + */ + private void addComponents() { + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + + setupTopPanel(); + setupInfoPanel(); + setupQuestionsPanel(); + setupButtonAndCheckBoxPanel(); + } + + // Sets action depending on allowApplet + checkbox state + private ActionListener chosenActionSetter(final boolean allowApplet) { + return new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + ExecuteUnsignedApplet action; + + if (allowApplet) { + action = permanencyCheckBox.isSelected() ? ExecuteUnsignedApplet.ALWAYS : ExecuteUnsignedApplet.YES; + } else { + action = permanencyCheckBox.isSelected() ? ExecuteUnsignedApplet.NEVER : ExecuteUnsignedApplet.NO; + } + + actionChoiceListener.actionChosen(action); + } + }; + } +}
\ No newline at end of file diff --git a/netx/net/sourceforge/jnlp/security/appletextendedsecurity/UnsignedAppletActionStorage.java b/netx/net/sourceforge/jnlp/security/appletextendedsecurity/UnsignedAppletActionStorage.java index 8ce6500..511b5fa 100644 --- a/netx/net/sourceforge/jnlp/security/appletextendedsecurity/UnsignedAppletActionStorage.java +++ b/netx/net/sourceforge/jnlp/security/appletextendedsecurity/UnsignedAppletActionStorage.java @@ -37,7 +37,6 @@ package net.sourceforge.jnlp.security.appletextendedsecurity; import java.util.List; - /** * This is abstract access to white/blacklist created from some permanent storage. * @@ -53,9 +52,9 @@ public interface UnsignedAppletActionStorage { /** * This methods iterates through records in - * DeploymentConfiguration.getAppletTrustSettingsPath(), and is mathing - * regexes saved here against params. so parameters here are NOR tegexes, - * but are matched against saved regexes + * DeploymentConfiguration.getAppletTrustSettingsPath(), and is matching + * regexes saved here against params. So parameters here are NOT regexes, + * but are matched against saved regexes. * * Null or empty values are dangerously ignored, user, be aware of it. eg: * match only codeBase will be null someCodeBase null null match only @@ -122,4 +121,14 @@ public interface UnsignedAppletActionStorage { * @param item */ public void update(final UnsignedAppletActionEntry item); + + /** + * Lock the storage, if necessary. If no ownership issues arise, can be a no-op. + */ + public void lock(); + + /** + * Unlock the storage, if necessary. If no ownership issues arise, can be a no-op. + */ + public void unlock(); } diff --git a/netx/net/sourceforge/jnlp/security/appletextendedsecurity/UnsignedAppletTrustConfirmation.java b/netx/net/sourceforge/jnlp/security/appletextendedsecurity/UnsignedAppletTrustConfirmation.java new file mode 100644 index 0000000..26bc1d4 --- /dev/null +++ b/netx/net/sourceforge/jnlp/security/appletextendedsecurity/UnsignedAppletTrustConfirmation.java @@ -0,0 +1,209 @@ +/* Copyright (C) 2013 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 net.sourceforge.jnlp.security.appletextendedsecurity; + +import static net.sourceforge.jnlp.runtime.Translator.R; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import net.sourceforge.jnlp.LaunchException; +import net.sourceforge.jnlp.PluginBridge; +import net.sourceforge.jnlp.cache.ResourceTracker; +import net.sourceforge.jnlp.security.SecurityDialogs; + +public class UnsignedAppletTrustConfirmation { + static private final boolean DEBUG = System.getenv().containsKey("ICEDTEAPLUGIN_DEBUG"); + + private static final AppletStartupSecuritySettings securitySettings = AppletStartupSecuritySettings.getInstance(); + + private static boolean unsignedConfirmationIsRequired() { + // If we are using the 'high' security setting or higher, we must confirm + // if the user wishes to run unsigned applets (not applicable to JNLP-launched apps) + return !(AppletSecurityLevel.ALLOW_UNSIGNED == securitySettings.getSecurityLevel()); + } + + private static boolean unsignedAppletsAreForbidden() { + // If we are using the 'very high' security setting or higher, we do not + // run unsigned applets + return AppletSecurityLevel.DENY_UNSIGNED == securitySettings.getSecurityLevel() + || AppletSecurityLevel.DENY_ALL == securitySettings.getSecurityLevel(); + } + + /** + * Gets the remembered decision, first checking the user policy for an ALWAYS/NEVER, + * and then the global policy. + * + * @param file the plugin file + * @return the remembered decision + */ + public static ExecuteUnsignedApplet getStoredAction(PluginBridge file) { + UnsignedAppletActionStorage userActionStorage = securitySettings.getUnsignedAppletActionCustomStorage(); + UnsignedAppletActionStorage globalActionStorage = securitySettings.getUnsignedAppletActionGlobalStorage(); + + UnsignedAppletActionEntry globalEntry = getMatchingItem(globalActionStorage, file); + UnsignedAppletActionEntry userEntry = getMatchingItem(userActionStorage, file); + + ExecuteUnsignedApplet globalAction = globalEntry == null ? null : globalEntry.getUnsignedAppletAction(); + ExecuteUnsignedApplet userAction = userEntry == null ? null : userEntry.getUnsignedAppletAction(); + + if (userAction == ExecuteUnsignedApplet.ALWAYS || userAction == ExecuteUnsignedApplet.NEVER) { + return userAction; + } else if (globalAction == ExecuteUnsignedApplet.ALWAYS || globalAction == ExecuteUnsignedApplet.NEVER) { + return globalAction; + } else { + return userAction; + } + } + + private static UnsignedAppletActionEntry getMatchingItem(UnsignedAppletActionStorage actionStorage, PluginBridge file) { + return actionStorage.getMatchingItem( + normalizeUrlAndStripParams(file.getSourceLocation()).toString(), + normalizeUrlAndStripParams(file.getCodeBase()).toString(), + toRelativePaths(file.getArchiveJars(), file.getCodeBase().toString())); + } + + static URL normalizeUrlAndStripParams(URL url) { + try { + String[] urlParts = url.toString().split("\\?"); + URL strippedUrl = new URL(urlParts[0]); + return ResourceTracker.normalizeUrl(strippedUrl, false); + } catch (IOException e) { + e.printStackTrace(); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + return url; + } + + /* Extract the archives as relative paths */ + static List<String> toRelativePaths(List<String> paths, String rootPath) { + List<String> fileNames = new ArrayList<String>(); + for (String path : paths) { + if (path.startsWith(rootPath)) { + fileNames.add(path.substring(rootPath.length())); + } else { + fileNames.add(path); + } + } + return fileNames; + } + + private static void updateAppletAction(PluginBridge file, ExecuteUnsignedApplet behaviour) { + + UnsignedAppletActionStorage userActionStorage = securitySettings.getUnsignedAppletActionCustomStorage(); + userActionStorage.lock(); // We should ensure this operation is atomic + try { + UnsignedAppletActionEntry oldEntry = getMatchingItem(userActionStorage, file); + + /* Update, if entry exists */ + if (oldEntry != null) { + oldEntry.setUnsignedAppletAction(behaviour); + oldEntry.setTimeStamp(new Date()); + userActionStorage.update(oldEntry); + return; + } + + URL codebase = normalizeUrlAndStripParams(file.getCodeBase()); + URL documentbase = normalizeUrlAndStripParams(file.getSourceLocation()); + + /* Else, create a new entry */ + UrlRegEx codebaseRegex = new UrlRegEx("\\Q" + codebase + "\\E"); + UrlRegEx documentbaseRegex = new UrlRegEx("\\Q" + documentbase + "\\E"); + + UnsignedAppletActionEntry entry = new UnsignedAppletActionEntry( + behaviour, + new Date(), + documentbaseRegex, + codebaseRegex, + toRelativePaths(file.getArchiveJars(), file.getCodeBase().toString())); + userActionStorage.add(entry); + } finally { + userActionStorage.unlock(); + } + } + static private void debug(String logMessage) { + if (DEBUG) { + System.err.println(logMessage); + } + + } + + public static void checkUnsignedWithUserIfRequired(PluginBridge file) throws LaunchException { + + if (unsignedAppletsAreForbidden()) { + debug("Not running unsigned applet at " + file.getCodeBase() +" because unsigned applets are disallowed by security policy."); + throw new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LUnsignedApplet"), R("LUnsignedAppletPolicyDenied")); + } + + if (!unsignedConfirmationIsRequired()) { + debug("Running unsigned applet at " + file.getCodeBase() +" does not require confirmation according to security policy."); + return; + } + + ExecuteUnsignedApplet storedAction = getStoredAction(file); + debug("Stored action for unsigned applet at " + file.getCodeBase() +" was " + storedAction); + + boolean appletOK; + + if (storedAction == ExecuteUnsignedApplet.ALWAYS) { + appletOK = true; + } else if (storedAction == ExecuteUnsignedApplet.NEVER) { + appletOK = false; + } else { + // No remembered decision, prompt the user + ExecuteUnsignedApplet decidedAction = SecurityDialogs.showUnsignedWarningDialog(file); + + appletOK = (decidedAction == ExecuteUnsignedApplet.YES || decidedAction == ExecuteUnsignedApplet.ALWAYS); + + if (decidedAction != null) { + updateAppletAction(file, decidedAction); + } + + debug("Decided action for unsigned applet at " + file.getCodeBase() +" was " + decidedAction); + } + + if (!appletOK) { + throw new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LUnsignedApplet"), R("LUnsignedAppletUserDenied")); + } + + } +}
\ No newline at end of file diff --git a/plugin/icedteanp/java/sun/applet/PluginAppletViewer.java b/plugin/icedteanp/java/sun/applet/PluginAppletViewer.java index 8a4f078..9151d5e 100644 --- a/plugin/icedteanp/java/sun/applet/PluginAppletViewer.java +++ b/plugin/icedteanp/java/sun/applet/PluginAppletViewer.java @@ -62,6 +62,8 @@ exception statement from your version. */ package sun.applet; +import static net.sourceforge.jnlp.runtime.Translator.R; + import java.applet.Applet; import java.applet.AppletContext; import java.applet.AudioClip; @@ -103,9 +105,13 @@ import java.util.concurrent.locks.ReentrantLock; import javax.swing.SwingUtilities; +import net.sourceforge.jnlp.LaunchException; import net.sourceforge.jnlp.NetxPanel; import net.sourceforge.jnlp.PluginParameters; import net.sourceforge.jnlp.runtime.JNLPClassLoader; +import net.sourceforge.jnlp.security.appletextendedsecurity.AppletSecurityLevel; +import net.sourceforge.jnlp.security.appletextendedsecurity.AppletStartupSecuritySettings; +import net.sourceforge.jnlp.security.appletextendedsecurity.ExecuteUnsignedApplet; import net.sourceforge.jnlp.splashscreen.SplashController; import net.sourceforge.jnlp.splashscreen.SplashPanel; import net.sourceforge.jnlp.splashscreen.SplashUtils; @@ -406,7 +412,12 @@ public class PluginAppletViewer extends XEmbeddedFrame requestFactory = rf; } - private static void handleInitializationMessage(int identifier, String message) throws IOException { + private static void handleInitializationMessage(int identifier, String message) throws IOException, LaunchException { + + /* The user has specified via a global setting that applets should not be run.*/ + if (AppletStartupSecuritySettings.getInstance().getSecurityLevel() == AppletSecurityLevel.DENY_ALL) { + throw new LaunchException(null, null, R("LSFatal"), R("LCClient"), R("LUnsignedApplet"), R("LUnsignedAppletPolicyDenied")); + } // If there is a key for this status, it means it // was either initialized before, or destroy has been diff --git a/tests/netx/unit/net/sourceforge/jnlp/security/appletextendedsecurity/UnsignedAppletTrustConfirmationTest.java b/tests/netx/unit/net/sourceforge/jnlp/security/appletextendedsecurity/UnsignedAppletTrustConfirmationTest.java new file mode 100644 index 0000000..be33f20 --- /dev/null +++ b/tests/netx/unit/net/sourceforge/jnlp/security/appletextendedsecurity/UnsignedAppletTrustConfirmationTest.java @@ -0,0 +1,59 @@ +package net.sourceforge.jnlp.security.appletextendedsecurity; + +import static org.junit.Assert.assertEquals; + +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +public class UnsignedAppletTrustConfirmationTest { + + private List<String> toList(String ... parts) { + List<String> list = new ArrayList<String>(); + for (String part : parts) { + list.add(part); + } + return list; + } + + @Test + public void testToRelativePaths() throws Exception { + /* Absolute -> Relative */ + assertEquals(toList("test.jar"), + UnsignedAppletTrustConfirmation.toRelativePaths(toList("http://example.com/test.jar"), "http://example.com/")); + + /* Relative is unchanged */ + assertEquals(toList("test.jar"), + UnsignedAppletTrustConfirmation.toRelativePaths(toList("test.jar"), "http://example.com/")); + + /* Different root URL is unchanged */ + assertEquals(toList("http://example2.com/test.jar"), + UnsignedAppletTrustConfirmation.toRelativePaths(toList("http://example2.com/test.jar"), "http://example.com/")); + + /* Path with invalid URL characters is handled */ + assertEquals(toList("test .jar"), + UnsignedAppletTrustConfirmation.toRelativePaths(toList("http://example.com/test .jar"), "http://example.com/")); + } + + @Test + public void testNormalizeUrlAndStripParams() throws Exception { + /* Test that URL is normalized (encoded if not already encoded, leading whitespace trimmed, etc) */ + assertEquals("http://example.com/%20test%20test", + UnsignedAppletTrustConfirmation.normalizeUrlAndStripParams(new URL("http://example.com/ test%20test ")).toString()); + /* Test that a URL without '?' is left unchanged */ + assertEquals("http://example.com/test", + UnsignedAppletTrustConfirmation.normalizeUrlAndStripParams(new URL("http://example.com/test")).toString()); + /* Test that parts of a URL that come after '?' are stripped */ + assertEquals("http://example.com/test", + UnsignedAppletTrustConfirmation.normalizeUrlAndStripParams(new URL("http://example.com/test?test=test")).toString()); + /* Test that everything after the first '?' is stripped */ + assertEquals("http://example.com/test", + UnsignedAppletTrustConfirmation.normalizeUrlAndStripParams(new URL("http://example.com/test?http://example.com/?test")).toString()); + + /* Test normalization + stripping */ + assertEquals("http://example.com/%20test%20test", + UnsignedAppletTrustConfirmation.normalizeUrlAndStripParams(new URL("http://www.example.com/ test%20test ?test=test")).toString()); + } +}
\ No newline at end of file |