changeset 656:1c26ce1e1cb3

Integration of unsigned applet confirmation dialogue.
author Adam Domurad <adomurad@redhat.com>
date Tue, 26 Mar 2013 14:57:33 -0400
parents bb971f25eb42
children b40198000d7c
files ChangeLog netx/net/sourceforge/jnlp/PluginBridge.java netx/net/sourceforge/jnlp/resources/Messages.properties netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java netx/net/sourceforge/jnlp/security/SecurityDialog.java netx/net/sourceforge/jnlp/security/SecurityDialogs.java netx/net/sourceforge/jnlp/security/UnsignedAppletTrustWarningDialog.java netx/net/sourceforge/jnlp/security/UnsignedAppletTrustWarningPanel.java netx/net/sourceforge/jnlp/security/appletextendedsecurity/UnsignedAppletActionStorage.java netx/net/sourceforge/jnlp/security/appletextendedsecurity/UnsignedAppletTrustConfirmation.java plugin/icedteanp/java/sun/applet/PluginAppletViewer.java tests/netx/unit/net/sourceforge/jnlp/security/appletextendedsecurity/UnsignedAppletTrustConfirmationTest.java
diffstat 12 files changed, 668 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Tue Mar 26 13:51:26 2013 +0100
+++ b/ChangeLog	Tue Mar 26 14:57:33 2013 -0400
@@ -1,3 +1,35 @@
+2013-03-26  Adam Domurad  <adomurad@redhat.com>
+
+	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 <jvanek@redhat.com>
 
 	Path validator fixed to be correctly multiplatform
--- a/netx/net/sourceforge/jnlp/PluginBridge.java	Tue Mar 26 13:51:26 2013 +0100
+++ b/netx/net/sourceforge/jnlp/PluginBridge.java	Tue Mar 26 14:57:33 2013 -0400
@@ -206,6 +206,10 @@
         }
     }
 
+    public List<String> getArchiveJars() {
+        return new ArrayList<String>(jars);
+    }
+
     public boolean codeBaseLookup() {
     	return params.useCodebaseLookup();
     }
--- a/netx/net/sourceforge/jnlp/resources/Messages.properties	Tue Mar 26 13:51:26 2013 +0100
+++ b/netx/net/sourceforge/jnlp/resources/Messages.properties	Tue Mar 26 14:57:33 2013 -0400
@@ -80,6 +80,9 @@
 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 @@
 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?
--- a/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java	Tue Mar 26 13:51:26 2013 +0100
+++ b/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java	Tue Mar 26 14:57:33 2013 -0400
@@ -63,6 +63,7 @@
 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 @@
         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 @@
 
         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 @@
                                     !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 @@
                 }
             } 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
             }
         }
 
--- a/netx/net/sourceforge/jnlp/security/SecurityDialog.java	Tue Mar 26 13:51:26 2013 +0100
+++ b/netx/net/sourceforge/jnlp/security/SecurityDialog.java	Tue Mar 26 14:57:33 2013 -0400
@@ -38,6 +38,7 @@
 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 @@
             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);
 
--- a/netx/net/sourceforge/jnlp/security/SecurityDialogs.java	Tue Mar 26 13:51:26 2013 +0100
+++ b/netx/net/sourceforge/jnlp/security/SecurityDialogs.java	Tue Mar 26 14:57:33 2013 -0400
@@ -37,6 +37,8 @@
 
 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 @@
         SINGLE_CERT_INFO,
         ACCESS_WARNING,
         NOTALLSIGNED_WARNING,
+        UNSIGNED_WARNING,   /* requires confirmation with 'high-security' setting */
         APPLET_WARNING,
         AUTHENTICATION,
     }
@@ -86,6 +89,7 @@
         VERIFIED,
         UNVERIFIED,
         NOTALLSIGNED,
+        UNSIGNED,           /* requires confirmation with 'high-security' setting */
         SIGNING_ERROR
     }
 
@@ -173,6 +177,26 @@
     }
 
     /**
+     * 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/netx/net/sourceforge/jnlp/security/UnsignedAppletTrustWarningDialog.java	Tue Mar 26 14:57:33 2013 -0400
@@ -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();
+                    }
+                })
+        );
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/netx/net/sourceforge/jnlp/security/UnsignedAppletTrustWarningPanel.java	Tue Mar 26 14:57:33 2013 -0400
@@ -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
--- a/netx/net/sourceforge/jnlp/security/appletextendedsecurity/UnsignedAppletActionStorage.java	Tue Mar 26 13:51:26 2013 +0100
+++ b/netx/net/sourceforge/jnlp/security/appletextendedsecurity/UnsignedAppletActionStorage.java	Tue Mar 26 14:57:33 2013 -0400
@@ -37,7 +37,6 @@
 
 import java.util.List;
 
-
 /**
  * This is abstract access to white/blacklist created from some permanent storage.
  * 
@@ -53,9 +52,9 @@
 
     /**
      * 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 @@
      * @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();
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/netx/net/sourceforge/jnlp/security/appletextendedsecurity/UnsignedAppletTrustConfirmation.java	Tue Mar 26 14:57:33 2013 -0400
@@ -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
--- a/plugin/icedteanp/java/sun/applet/PluginAppletViewer.java	Tue Mar 26 13:51:26 2013 +0100
+++ b/plugin/icedteanp/java/sun/applet/PluginAppletViewer.java	Tue Mar 26 14:57:33 2013 -0400
@@ -62,6 +62,8 @@
 
 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 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 @@
         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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/netx/unit/net/sourceforge/jnlp/security/appletextendedsecurity/UnsignedAppletTrustConfirmationTest.java	Tue Mar 26 14:57:33 2013 -0400
@@ -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