From 7c527f585eac6c2dbc8c9ffdd3597e3f5c9cd7ad Mon Sep 17 00:00:00 2001 From: David Tavoularis Date: Tue, 15 Dec 2020 14:11:01 +0100 Subject: [PATCH 1/9] Better HTTPS with Client Authentication & SmartCard support on Windows a. Better HTTPS with Client authentication : - In order to avoid too many PIN/Password requests (HTTPS hanshakes), downloads first the initial jar (mono-thread) then all remaining ones (multi-thread) - In case of initial jar download failure (Client Certificate Selection cancel, incorrect PIN/Password, ...), fail fast and do not try to download remaining jars - No resource prefetch, otherwise deployment.cache.parallelDownloadCount is not enforced b. New Security Dialog UI for Client Certificate Selection : - Ability to display details on each certificate - Shown in TaskBar - Cancellable - Selection using double-click, Enter key or OK button - Internationalization c. SmartCard support on Windows - Introduction of a Merged Key Manager for user, system and browser key stores - Client alias selection using a preference algorithm : a) Get all non-expired aliases with extension ClientCert (1.3.6.1.5.5.7.3.2) or ANY (2.5.29.37.0) if only one, it is selected, if more than one, a UI helps to select one of them. b) Otherwise, get all expired aliases with extension ClientCert (1.3.6.1.5.5.7.3.2) or ANY (2.5.29.37.0) if only one, it is selected, if more than one, a UI helps to select one of them. c) Otherwise, get all remaining aliases if only one, it is selected, if more than one, a UI helps to select one of them. - UI will display the suffix " (from user keystore)" or " (from system keystore)" or " (from browser keystore)" - If user cancels the Client Certificate UI, it is remembered for next chooseClientAlias call - Client alias selection is cached per remote host --- AUTHORS | 1 + .../security/ClientCertSelectionPane.java | 240 +++++++++++++++ .../dialogs/security/SecurityDialog.java | 15 +- .../security/SecurityDialogMessage.java | 1 + .../SecurityDialogMessageHandler.java | 2 +- .../dialogs/security/SecurityDialogs.java | 19 +- .../parts/dialogs/security/ViwableDialog.java | 6 +- .../icedteaweb/resources/ResourceTracker.java | 4 + .../net/sourceforge/jnlp/cache/CacheUtil.java | 7 + .../sourceforge/jnlp/runtime/JNLPRuntime.java | 11 +- .../jnlp/runtime/MergedKeyManager.java | 287 ++++++++++++++++++ .../runtime/classloader/JNLPClassLoader.java | 4 +- 12 files changed, 578 insertions(+), 19 deletions(-) create mode 100644 core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ClientCertSelectionPane.java create mode 100644 core/src/main/java/net/sourceforge/jnlp/runtime/MergedKeyManager.java diff --git a/AUTHORS b/AUTHORS index 8d40f0d8b..ed10652bc 100644 --- a/AUTHORS +++ b/AUTHORS @@ -39,6 +39,7 @@ Ville Skyttä Fridrich Strba Andrew Su Joshua Sumali +David Tavoularis Joel Tesdall Michal Vala Jiri Vanek diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ClientCertSelectionPane.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ClientCertSelectionPane.java new file mode 100644 index 000000000..d5ca236bc --- /dev/null +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ClientCertSelectionPane.java @@ -0,0 +1,240 @@ +package net.adoptopenjdk.icedteaweb.client.parts.dialogs.security; + +/* ClientCertSelectionPane.java -- requests client cert selection from users + +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. */ + +import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.DialogResult; +import net.sourceforge.jnlp.security.SecurityUtil; + +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.ListSelectionModel; + +import java.awt.BorderLayout; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.Map.Entry; + +import static net.adoptopenjdk.icedteaweb.i18n.Translator.R; + +/** + * Modal non-minimizable dialog to request client cert selection + */ +public class ClientCertSelectionPane extends SecurityDialogPanel { + + public ClientCertSelectionPane(SecurityDialog parent, Object[] extras) { + super(parent); + setLayout(new GridBagLayout()); + JLabel jlInfo = new JLabel("" + R("CVCertificateViewer") + ""); + + @SuppressWarnings("unchecked") LinkedHashMap aliasesMap = (LinkedHashMap)extras[0]; + ArrayList displayedNames = new ArrayList(); + for (Entry entry : aliasesMap.entrySet()) { + String subject = SecurityUtil.getCN(entry.getValue().getSubjectX500Principal().getName()); + String issuer = SecurityUtil.getCN(entry.getValue().getIssuerX500Principal().getName()); + int pos = entry.getKey().lastIndexOf(" (from "); + String source = (pos!=-1)?entry.getKey().substring(pos):""; + displayedNames.add(subject + ":" + issuer + source); + } + + JList jlist = new JList(displayedNames.toArray(new String[0])); + jlist.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + jlist.setSelectedIndex(0); + JScrollPane scrollPane = new JScrollPane(jlist); + + JButton jbOK = new JButton(R("ButOk")); + JButton jbCancel = new JButton(R("ButCancel")); + jbOK.setPreferredSize(jbCancel.getPreferredSize()); + JButton jbDetails = new JButton(R("ButShowDetails")); + jbDetails.setBorderPainted(false); + jbDetails.setContentAreaFilled(false); + jbDetails.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + jbDetails.setMargin(new Insets(0, 0, 0, 0)); + + GridBagConstraints c = new GridBagConstraints(); + c.anchor = GridBagConstraints.NORTHWEST; + c.gridx = 0; + c.gridy = 0; + c.weightx = 1.0; + c.insets = new Insets(10, 5, 3, 3); + add(jlInfo, c); + + c = new GridBagConstraints(); + c.fill = GridBagConstraints.BOTH; + c.gridx = 0; + c.gridy = 1; + c.weightx = 1.0; + c.weighty = 1.0; + c.insets = new Insets(10, 5, 3, 3); + add(scrollPane, c); + + c = new GridBagConstraints(); + c.anchor = GridBagConstraints.SOUTHEAST; + c.gridx = 0; + c.gridy = 2; + c.weightx = 1.0; + c.insets = new Insets(5, 5, 3, 3); + add(jbDetails, c); + + c = new GridBagConstraints(); + c.anchor = GridBagConstraints.SOUTHEAST; + c.gridx = 0; + c.gridy = 3; + c.weightx = 1.0; + c.insets = new Insets(3, 3, 10, 10); + + JPanel okCancelPane = new JPanel(new FlowLayout(FlowLayout.TRAILING, 0, 0)); + okCancelPane.add(jbOK); + okCancelPane.add(Box.createHorizontalStrut(10)); + okCancelPane.add(jbCancel); + add(okCancelPane, c); + + if (parent!=null) { + parent.getViwableDialog().setMinimumSize(new Dimension(500, 300)); + parent.getViwableDialog().setLocationRelativeTo(null); + parent.getViwableDialog().pack(); + } + + initialFocusComponent = scrollPane; + + // click on OK + jbOK.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + certSelected(parent, jlist); + } + }); + // double-click on selection + jlist.addMouseListener(new MouseAdapter() { + @Override public void mouseClicked(MouseEvent me) { + if (me.getClickCount() == 2) + certSelected(parent, jlist); + } + }); + // enter on selection + jlist.addKeyListener(new KeyAdapter() { + @Override public void keyReleased(KeyEvent ke) { + if(ke.getKeyCode() == KeyEvent.VK_ENTER) + certSelected(parent, jlist); + } + }); + // open certificate details + jbDetails.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + SecurityDialog.showSingleCertInfoDialog(aliasesMap.values().toArray(new X509Certificate[0])[jlist.getSelectedIndex()], + ClientCertSelectionPane.this); + } + }); + // click on Cancel + jbCancel.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + parent.setValue(null); + parent.getViwableDialog().dispose(); + } + }); + } + + @Override + public DialogResult getDefaultNegativeAnswer() { + return null; + } + + @Override + public DialogResult getDefaultPositiveAnswer() { + return null; + } + + @Override + public DialogResult readFromStdIn(String what) { + return null; + } + + @Override + public String helpToStdIn() { + return ""; + } + + private void certSelected(SecurityDialog parent, JList jlist) { + parent.setValue(new DialogResult() { + @Override public int getButtonIndex() { + return jlist.getSelectedIndex(); + } + @Override public boolean toBoolean() { + throw new UnsupportedOperationException("Not supported yet."); + } + @Override public String writeValue() { + throw new UnsupportedOperationException("Not supported yet."); + } + }); + parent.getViwableDialog().dispose(); + } + + public static void main(String[] args) throws Exception { + KeyStore ks = KeyStore.getInstance("Windows-MY"); + ks.load(null, null); + Enumerationaliases = ks.aliases(); + LinkedHashMap aliasesMap = new LinkedHashMap(); + while (aliases.hasMoreElements()) { + String alias = aliases.nextElement(); + Certificate c = ks.getCertificate(alias); + if (c instanceof X509Certificate) + aliasesMap.put(alias + " (from browser keystore)", (X509Certificate)c); + } + JFrame f = new JFrame(); + f.setMinimumSize(new Dimension(500, 300)); + f.setSize(700, 300); + f.add(new ClientCertSelectionPane(null, new Object[] { aliasesMap }), BorderLayout.CENTER); + f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + f.pack(); + f.setVisible(true); + } +} \ No newline at end of file diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialog.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialog.java index 1955314b7..b6d8d7722 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialog.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialog.java @@ -98,8 +98,8 @@ public class SecurityDialog { private boolean requiresSignedJNLPWarning; SecurityDialog(SecurityDialogs.DialogType dialogType, AccessType accessType, - JNLPFile file, CertVerifier JarCertVerifier, X509Certificate cert, Object[] extras) { - this.viwableDialog = new ViwableDialog(); + JNLPFile file, CertVerifier JarCertVerifier, X509Certificate cert, Object[] extras, boolean showInTaskBar) { + this.viwableDialog = new ViwableDialog(showInTaskBar); this.dialogType = dialogType; this.accessType = accessType; this.file = file; @@ -114,6 +114,11 @@ public class SecurityDialog { initDialog(); } + SecurityDialog(SecurityDialogs.DialogType dialogType, AccessType accessType, + JNLPFile file, CertVerifier JarCertVerifier, X509Certificate cert, Object[] extras) { + this(dialogType, accessType, file, JarCertVerifier, cert, extras, false); + } + /** * Construct a SecurityDialog to display some sort of access warning */ @@ -204,7 +209,7 @@ public static void showCertInfoDialog(CertVerifier certVerifier, * @param parent the parent pane. */ public static void showSingleCertInfoDialog(X509Certificate c, - Window parent) { + Component parent) { SecurityDialog dialog = new SecurityDialog(SecurityDialogs.DialogType.SINGLE_CERT_INFO, c); dialog.getViwableDialog().setLocationRelativeTo(parent); dialog.getViwableDialog().setModalityType(ModalityType.APPLICATION_MODAL); @@ -281,7 +286,7 @@ else if (dtype == SecurityDialogs.DialogType.APPLET_WARNING) dialogTitle = "Applet Warning"; else if (dtype == SecurityDialogs.DialogType.PARTIALLY_SIGNED_WARNING) dialogTitle = "Security Warning"; - else if (dtype == SecurityDialogs.DialogType.AUTHENTICATION) + else if (dtype == SecurityDialogs.DialogType.AUTHENTICATION || dtype == SecurityDialogs.DialogType.CLIENT_CERT_SELECTION) dialogTitle = "Authentication Required"; return dialogTitle; } @@ -336,6 +341,8 @@ static SecurityDialogPanel getPanel(SecurityDialogs.DialogType type, SecurityDia lpanel = AppTrustWarningDialog.unsigned(sd, sd.file); // Only necessary for applets on 'high security' or above } else if (type == SecurityDialogs.DialogType.AUTHENTICATION) { lpanel = new PasswordAuthenticationPane(sd, sd.extras); + } else if (type == SecurityDialogs.DialogType.CLIENT_CERT_SELECTION) { + lpanel = new ClientCertSelectionPane(sd, sd.extras); } else if (type == SecurityDialogs.DialogType.UNSIGNED_EAS_NO_PERMISSIONS_WARNING) { lpanel = new MissingPermissionsAttributePanel(sd, sd.file.getTitle(), sd.file.getNotNullProbableCodeBase().toExternalForm()); } else if (type == SecurityDialogs.DialogType.MISSING_ALACA) { diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogMessage.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogMessage.java index 4b2bfa939..8f2a38a1c 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogMessage.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogMessage.java @@ -81,5 +81,6 @@ public SecurityDialogMessage(JNLPFile file) { public Semaphore lock; //if dialog slip out of awt thread, fake modal dialog is created. This is keeping it. public JDialog toDispose; + public boolean showInTaskBar; } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogMessageHandler.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogMessageHandler.java index 6be4f0c76..db8f93265 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogMessageHandler.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogMessageHandler.java @@ -110,7 +110,7 @@ public void run() { protected void handleMessage(final SecurityDialogMessage message) { final SecurityDialog dialog = new SecurityDialog(message.dialogType, - message.accessType, message.file, message.certVerifier, message.certificate, message.extras); + message.accessType, message.file, message.certVerifier, message.certificate, message.extras, message.showInTaskBar); if (processAutomatedAnswers(message, dialog)){ return; diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogs.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogs.java index 59ce9fdcc..ec421769e 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogs.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogs.java @@ -51,6 +51,8 @@ import net.sourceforge.jnlp.util.UrlUtils; import javax.swing.JDialog; + +import java.awt.Dialog; import java.awt.Dialog.ModalityType; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; @@ -58,6 +60,8 @@ import java.net.URL; import java.security.AccessController; import java.security.PrivilegedAction; +import java.security.cert.X509Certificate; +import java.util.LinkedHashMap; import java.util.Set; import java.util.concurrent.Semaphore; @@ -92,6 +96,7 @@ public static enum DialogType { UNSIGNED_WARNING, /* requires confirmation with 'high-security' setting */ APPLET_WARNING, AUTHENTICATION, + CLIENT_CERT_SELECTION, UNSIGNED_EAS_NO_PERMISSIONS_WARNING, /* when Extended applet security is at High Security and no permission attribute is find, */ MISSING_ALACA, /*alaca - Application-Library-Allowable-Codebase Attribute*/ MATCHING_ALACA, @@ -203,7 +208,6 @@ public static YesNoSandbox showPartiallySignedWarningDialog(JNLPFile file, CertV * permissions. */ public static NamePassword showAuthenticationPrompt(String host, int port, String prompt, String type) { - LOG.debug("Showing dialog for basic auth for {}:{} with name {} of type {}", host, port, prompt, type); SecurityManager sm = System.getSecurityManager(); if (sm != null) { @@ -218,8 +222,21 @@ public static NamePassword showAuthenticationPrompt(String host, int port, Strin message.extras = new Object[]{host, port, prompt, type}; DialogResult response = getUserResponse(message); + LOG.debug("Decided action for matching alaca at was {}", response); return (NamePassword) response; } + + public static DialogResult showClientCertSelectionPrompt(LinkedHashMap aliases) { + + final SecurityDialogMessage message = new SecurityDialogMessage(null); + message.dialogType = DialogType.CLIENT_CERT_SELECTION; + message.extras = new Object[] {aliases}; + message.showInTaskBar = true; + + DialogResult response = getUserResponse(message); + LOG.debug("Decided action for selecting client certificate was {}", response); + return response; + } public static boolean showMissingALACAttributePanel(JNLPFile file, URL codeBase, Set remoteUrls) { diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ViwableDialog.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ViwableDialog.java index 8ff3bf791..0240bcdb4 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ViwableDialog.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ViwableDialog.java @@ -57,13 +57,15 @@ public class ViwableDialog { private JDialog jd = null; + private boolean showInTaskBar; List operations = new ArrayList(); - public ViwableDialog() { + public ViwableDialog(boolean showInTaskBar) { + this.showInTaskBar = showInTaskBar; } private JDialog createJDialog() { - jd = new JDialog(); + jd = showInTaskBar?new JDialog((Dialog)null):new JDialog(); jd.setName("ViwableDialog"); SwingUtils.info(jd); jd.setIconImages(ImageResources.INSTANCE.getApplicationImages()); diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/ResourceTracker.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/ResourceTracker.java index b879331cf..ceb8f85cc 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/ResourceTracker.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/ResourceTracker.java @@ -21,6 +21,7 @@ import net.adoptopenjdk.icedteaweb.logging.Logger; import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; import net.adoptopenjdk.icedteaweb.resources.CachedDaemonThreadPoolProvider.DaemonThreadFactory; +import net.adoptopenjdk.icedteaweb.resources.Resource.Status; import net.sourceforge.jnlp.DownloadOptions; import net.sourceforge.jnlp.cache.CacheUtil; import net.sourceforge.jnlp.config.ConfigurationConstants; @@ -396,6 +397,9 @@ private void waitForCompletion(Resource... resources) { LOG.debug("Download done. Shutting down executor"); downloadExecutor.shutdownNow(); } + + if (resources.length == 1 && resources[0].isSet(Status.ERROR)) + throw new RuntimeException("Error while downloading initial resource " + resources[0]); } private Future triggerDownloadFor(Resource resource, final Executor downloadExecutor) { diff --git a/core/src/main/java/net/sourceforge/jnlp/cache/CacheUtil.java b/core/src/main/java/net/sourceforge/jnlp/cache/CacheUtil.java index 08de9aa7e..b741d0e0a 100644 --- a/core/src/main/java/net/sourceforge/jnlp/cache/CacheUtil.java +++ b/core/src/main/java/net/sourceforge/jnlp/cache/CacheUtil.java @@ -234,6 +234,8 @@ public static String hex(String origName, String candidate) throws NoSuchAlgorit * @param title name of the download */ public static void waitForResources(final JNLPClassLoader jnlpClassLoader, final ResourceTracker tracker, final URL[] resources, final String title) { + // download first initial jar : so in case of client certificate, only 1 https client handshake is done + boolean downloadInitialJarFirst = resources.length > 1 && resources[0].getProtocol().equals("https"); try { final DownloadIndicator indicator = Optional.ofNullable(JNLPRuntime.getDefaultDownloadIndicator()) .orElseGet(() -> new DummyDownloadIndicator()); @@ -242,12 +244,17 @@ public static void waitForResources(final JNLPClassLoader jnlpClassLoader, final for (URL url : resources) { tracker.addDownloadListener(url, resources, listener); } + if (downloadInitialJarFirst) + tracker.waitForResources(resources[0]); + // download all remaining ones tracker.waitForResources(resources); } finally { indicator.disposeListener(listener); } } catch (Exception ex) { LOG.error("Downloading of resources ended with error", ex); + if (downloadInitialJarFirst) + throw new RuntimeException(ex); } } diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/JNLPRuntime.java b/core/src/main/java/net/sourceforge/jnlp/runtime/JNLPRuntime.java index 87a93e45f..417bcd01e 100644 --- a/core/src/main/java/net/sourceforge/jnlp/runtime/JNLPRuntime.java +++ b/core/src/main/java/net/sourceforge/jnlp/runtime/JNLPRuntime.java @@ -35,8 +35,6 @@ import net.sourceforge.jnlp.config.DeploymentConfiguration; import net.sourceforge.jnlp.config.PathsAndFiles; import net.sourceforge.jnlp.security.JNLPAuthenticator; -import net.sourceforge.jnlp.security.KeyStores; -import net.sourceforge.jnlp.security.SecurityUtil; import net.sourceforge.jnlp.services.XServiceManagerStub; import net.sourceforge.jnlp.util.logging.LogConfig; import net.sourceforge.jnlp.util.logging.OutputController; @@ -45,7 +43,7 @@ import javax.jnlp.ServiceManager; import javax.naming.ConfigurationException; import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; @@ -69,7 +67,6 @@ import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.security.AllPermission; -import java.security.KeyStore; import java.security.Policy; import java.security.Security; import java.text.DateFormat; @@ -271,13 +268,9 @@ public static void initialize() throws IllegalStateException { try { SSLSocketFactory sslSocketFactory; SSLContext context = SSLContext.getInstance("SSL"); - KeyStore ks = KeyStores.getKeyStore(KeyStores.Level.USER, KeyStores.Type.CLIENT_CERTS).getKs(); - KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); - SecurityUtil.initKeyManagerFactory(kmf, ks); TrustManager[] trust = new TrustManager[] { getSSLSocketTrustManager() }; - context.init(kmf.getKeyManagers(), trust, null); + context.init(new KeyManager[] { new MergedKeyManager() }, trust, null); sslSocketFactory = context.getSocketFactory(); - HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory); } catch (Exception e) { LOG.error("Unable to set SSLSocketfactory (may _prevent_ access to sites that should be trusted)! Continuing anyway...", e); diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/MergedKeyManager.java b/core/src/main/java/net/sourceforge/jnlp/runtime/MergedKeyManager.java new file mode 100644 index 000000000..17e386b42 --- /dev/null +++ b/core/src/main/java/net/sourceforge/jnlp/runtime/MergedKeyManager.java @@ -0,0 +1,287 @@ +package net.sourceforge.jnlp.runtime; + +import java.lang.reflect.Method; +import java.net.Socket; +import java.security.KeyStore; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.CertificateException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.X509ExtendedKeyManager; +import javax.net.ssl.X509KeyManager; + +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialogs; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.DialogResult; +import net.sourceforge.jnlp.security.KeyStores; +import net.sourceforge.jnlp.security.SecurityUtil; + +public class MergedKeyManager extends X509ExtendedKeyManager +{ + private final static Logger LOG = LoggerFactory.getLogger(MergedKeyManager.class); + private static Map hostToClientAliasCache = new ConcurrentHashMap(); + private X509KeyManager browserKeyManager; + private X509KeyManager userKeyManager; + private X509KeyManager systemKeyManager; + private final static String USER_SUFFIX = " (from user keystore)"; + private final static String SYSTEM_SUFFIX = " (from system keystore)"; + private final static String BROWSER_SUFFIX = " (from browser keystore)"; + private static boolean userCancelled = false; + + MergedKeyManager() { + userKeyManager = getKeyManager(KeyStores.getKeyStore(KeyStores.Level.USER, KeyStores.Type.CLIENT_CERTS).getKs(), "SunX509"); + systemKeyManager = getKeyManager(KeyStores.getKeyStore(KeyStores.Level.SYSTEM, KeyStores.Type.CLIENT_CERTS).getKs(), "SunX509"); + if (System.getProperty("os.name").startsWith("Windows")) { + try { + KeyStore ks = KeyStore.getInstance("Windows-MY"); + ks.load(null, null); + browserKeyManager = getKeyManager(ks, "SunX509"); + } + catch (Exception e) { + LOG.error("Unable to get browser keystore information", e); + } + } + } + + private X509KeyManager getKeyManager(KeyStore ks, String algo) { + try { + KeyManagerFactory kmf = KeyManagerFactory.getInstance(algo); + SecurityUtil.initKeyManagerFactory(kmf, ks); + KeyManager[] keyManagers = kmf.getKeyManagers(); + if (keyManagers != null) + for (KeyManager keyManager : keyManagers) + if (keyManager instanceof X509KeyManager) + return (X509KeyManager)keyManager; + } + catch (Exception e) { + LOG.warn("Unable to get KeyStore " + ks, e); + } + return null; + } + + @Override public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) { + if (userCancelled) { + LOG.warn("Client certificate selection previously cancelled by user, returning null alias"); + return null; + } + + String host = getHostFromSocket(socket); + LOG.info("Retrieved host from socket : " + host); + if (host != null) { + String alias = hostToClientAliasCache.get(host.toLowerCase()); + LOG.info("Found " + alias + " alias in cache for " + host.toLowerCase() + " and keyTypes " + Arrays.toString(keyTypes)); + if (alias != null) + return alias; + } + + LinkedHashMap validClientAliases = new LinkedHashMap<>(); + LinkedHashMap expiredClientAliases = new LinkedHashMap<>(); + LinkedHashMap otherAliases = new LinkedHashMap<>(); + for (String keyType : keyTypes) { + String[] aliasesWithSuffix = getClientAliases(keyType, issuers); + //if (aliasesWithSuffix != null) + for (String aliasWithSuffix : aliasesWithSuffix) + { + X509Certificate[] certs = getCertificateChain(removeAliasSuffix(aliasWithSuffix)); + if (certs == null || certs.length == 0) + continue; + X509Certificate cert = certs[0]; + try { + List usage = cert.getExtendedKeyUsage(); + // Extensions : 1.3.6.1.5.5.7.3.2=ClientCert 2.5.29.37.0=ANY + if (usage != null && (usage.contains("1.3.6.1.5.5.7.3.2") || usage.contains("2.5.29.37.0"))) { + try { + cert.checkValidity(); + LOG.info("Found valid client alias with clientCert or ANY extension: " + aliasWithSuffix); + validClientAliases.put(aliasWithSuffix, cert); + } + catch (CertificateException e) { + LOG.warn("Found expired client alias with clientCert or ANY extension: " + aliasWithSuffix); + expiredClientAliases.put(aliasWithSuffix, cert); + } + } + else { + LOG.warn("Found non-client alias: " + aliasWithSuffix); + otherAliases.put(aliasWithSuffix, cert); + } + } + catch (CertificateParsingException e) { + LOG.warn("Exception while getting ExtendedKeyUsage for alias " + aliasWithSuffix); + otherAliases.put(aliasWithSuffix, cert); + } + } + } + + String alias = null; + if (!validClientAliases.isEmpty()) + alias = getPreferredAlias(validClientAliases, "valid client"); + else { + expiredClientAliases.putAll(otherAliases); + if (!expiredClientAliases.isEmpty()) + alias = getPreferredAlias(expiredClientAliases, "remaining"); + else + LOG.warn("Could not find any client alias for keyTypes " + Arrays.toString(keyTypes)); + } + + if (socket instanceof SSLSocket && host != null && alias != null) { + LOG.info("Added " + alias + " alias in cache for " + host.toLowerCase()); + hostToClientAliasCache.put(host.toLowerCase(), alias); + } + return alias; + } + + private String getHostFromSocket(Socket socket) { + try { + Class c = Class.forName("sun.security.ssl.SSLSocketImpl"); + if (c.isInstance(socket)) { + Object o = c.cast(socket); + Method m = null; + try { + m = c.getDeclaredMethod("getHost", null); + m.setAccessible(true); + } + catch (NoSuchMethodException e) { + m = c.getDeclaredMethod("getPeerHost", null); + } + if (m != null) + return (String)m.invoke(o, null); + } + } + catch (Exception e) { + LOG.warn("Cannot get remote host from Socket", e); + } + return null; + } + + private String getPreferredAlias(LinkedHashMap aliasesMap, String aliasType) { + String alias = null; + if (aliasesMap.size() > 1) { + if (JNLPRuntime.isHeadless()) { + alias = aliasesMap.keySet().iterator().next(); + LOG.info("Returning the first " + aliasType + " alias in headless mode : " + alias); + } + else { + DialogResult res = SecurityDialogs.showClientCertSelectionPrompt(aliasesMap); + if (res == null) { + userCancelled = true; + LOG.warn("Client certificate selection cancelled by user"); + } + else { + alias = aliasesMap.keySet().toArray(new String[0])[res.getButtonIndex()]; + LOG.info("Returning the selected " + aliasType + " alias : " + alias); + } + } + } + else if (aliasesMap.size() == 1) { + alias = aliasesMap.keySet().iterator().next(); + LOG.info("Returning the only " + aliasType + " alias : " + alias); + } + return removeAliasSuffix(alias); + } + + private String removeAliasSuffix(String aliasWithSuffix) { + if (aliasWithSuffix.endsWith(USER_SUFFIX)) + return aliasWithSuffix.substring(0, aliasWithSuffix.length() - USER_SUFFIX.length()); + if (aliasWithSuffix.endsWith(SYSTEM_SUFFIX)) + return aliasWithSuffix.substring(0, aliasWithSuffix.length() - SYSTEM_SUFFIX.length()); + if (aliasWithSuffix.endsWith(BROWSER_SUFFIX)) + return aliasWithSuffix.substring(0, aliasWithSuffix.length() - BROWSER_SUFFIX.length()); + return aliasWithSuffix; + } + + public static String getAliasSuffix(String aliasWithSuffix) { + if (aliasWithSuffix.endsWith(USER_SUFFIX)) + return USER_SUFFIX; + if (aliasWithSuffix.endsWith(SYSTEM_SUFFIX)) + return SYSTEM_SUFFIX; + if (aliasWithSuffix.endsWith(BROWSER_SUFFIX)) + return BROWSER_SUFFIX; + return null; + } + + @Override public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { + String alias = null; + if (userKeyManager != null) + alias = userKeyManager.chooseServerAlias(keyType, issuers, socket); + if (alias == null && systemKeyManager != null) + alias = systemKeyManager.chooseServerAlias(keyType, issuers, socket); + if (alias == null && browserKeyManager != null) + alias = browserKeyManager.chooseServerAlias(keyType, issuers, socket); + return alias; + } + + @Override public X509Certificate[] getCertificateChain(String alias) { + X509Certificate[] x509certificates = null; + if (userKeyManager != null) + x509certificates = userKeyManager.getCertificateChain(alias); + if (x509certificates == null && systemKeyManager != null) + x509certificates = systemKeyManager.getCertificateChain(alias); + if (x509certificates == null && browserKeyManager != null) + x509certificates = browserKeyManager.getCertificateChain(alias); + return x509certificates; + } + + @Override public String[] getClientAliases(String keyType, Principal[] issuers) { + List aliases = new ArrayList(); + if (userKeyManager != null) + addNonNullStrings(aliases, userKeyManager.getClientAliases(keyType, issuers), USER_SUFFIX); + if (systemKeyManager != null) + addNonNullStrings(aliases, systemKeyManager.getClientAliases(keyType, issuers), SYSTEM_SUFFIX); + if (browserKeyManager != null) + addNonNullStrings(aliases, browserKeyManager.getClientAliases(keyType, issuers), BROWSER_SUFFIX); + return aliases.toArray(new String[0]); + } + + @Override public PrivateKey getPrivateKey(String alias) { + PrivateKey privateKey = null; + if (userKeyManager != null) + privateKey = userKeyManager.getPrivateKey(alias); + if (privateKey == null && systemKeyManager != null) + privateKey = systemKeyManager.getPrivateKey(alias); + if (privateKey == null && browserKeyManager != null) + privateKey = browserKeyManager.getPrivateKey(alias); + return privateKey; + } + + @Override public String[] getServerAliases(String keyType, Principal[] issuers) { + List aliases = new ArrayList(); + if (userKeyManager != null) + addNonNullStrings(aliases, userKeyManager.getServerAliases(keyType, issuers), null); + if (systemKeyManager != null) + addNonNullStrings(aliases, systemKeyManager.getServerAliases(keyType, issuers), null); + if (browserKeyManager != null) + addNonNullStrings(aliases, browserKeyManager.getServerAliases(keyType, issuers), null); + return aliases.toArray(new String[0]); + } + + @Override public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) { + return chooseClientAlias(keyType, issuers, null); + } + + @Override public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) { + return chooseServerAlias(keyType, issuers, null); + } + + private void addNonNullStrings(List list, String[] array, String suffix) { + if (array != null) + for (String s : array) + if (s != null) + if (suffix != null) + list.add(s + suffix); + else + list.add(s); + } +} diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/classloader/JNLPClassLoader.java b/core/src/main/java/net/sourceforge/jnlp/runtime/classloader/JNLPClassLoader.java index 6830336d6..50c9a878f 100644 --- a/core/src/main/java/net/sourceforge/jnlp/runtime/classloader/JNLPClassLoader.java +++ b/core/src/main/java/net/sourceforge/jnlp/runtime/classloader/JNLPClassLoader.java @@ -316,7 +316,8 @@ private JNLPClassLoader(JNLPFile file, UpdatePolicy policy, String mainName, boo strict = Boolean.parseBoolean(JNLPRuntime.getConfiguration().getProperty(ConfigurationConstants.KEY_STRICT_JNLP_CLASSLOADER)); this.file = file; - this.tracker = new ResourceTracker(true, file.getDownloadOptions(), JNLPRuntime.getDefaultUpdatePolicy()); + // no prefetch otherwise deployment.cache.parallelDownloadCount is not enforced + this.tracker = new ResourceTracker(false, file.getDownloadOptions(), JNLPRuntime.getDefaultUpdatePolicy()); this.updatePolicy = policy; this.resources = file.getResources(); @@ -684,7 +685,6 @@ private void initializeResources() throws LaunchException { if (jar.isEager() || jar.isMain()) { initialJars.add(jar); // regardless of part } - // FIXME: this will trigger an eager download as the tracker is created with prefetch == true tracker.addResource(jar.getLocation(), jar.getVersion(), jar.isCacheable() ? JNLPRuntime.getDefaultUpdatePolicy() : UpdatePolicy.FORCE); } From 6b96e267ef0d78e7d7933ef37065baa9ed0f5594 Mon Sep 17 00:00:00 2001 From: Stephan Classen Date: Wed, 21 Apr 2021 10:21:48 +0200 Subject: [PATCH 2/9] code formatting --- .../security/ClientCertSelectionPane.java | 366 ++++++++++-------- 1 file changed, 195 insertions(+), 171 deletions(-) diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ClientCertSelectionPane.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ClientCertSelectionPane.java index d5ca236bc..ebc1a9429 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ClientCertSelectionPane.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ClientCertSelectionPane.java @@ -1,6 +1,5 @@ -package net.adoptopenjdk.icedteaweb.client.parts.dialogs.security; - -/* ClientCertSelectionPane.java -- requests client cert selection from users +/* ClientCertSelectionPane.java + Copyright (C) 2021 Karakun AG. This file is part of IcedTea. @@ -30,7 +29,10 @@ 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. */ +statement from your version. +*/ + +package net.adoptopenjdk.icedteaweb.client.parts.dialogs.security; import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.DialogResult; import net.sourceforge.jnlp.security.SecurityUtil; @@ -43,7 +45,6 @@ import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.ListSelectionModel; - import java.awt.BorderLayout; import java.awt.Cursor; import java.awt.Dimension; @@ -63,8 +64,11 @@ import java.util.ArrayList; import java.util.Enumeration; import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import static net.adoptopenjdk.icedteaweb.Assert.requireNonNull; import static net.adoptopenjdk.icedteaweb.i18n.Translator.R; /** @@ -72,169 +76,189 @@ */ public class ClientCertSelectionPane extends SecurityDialogPanel { - public ClientCertSelectionPane(SecurityDialog parent, Object[] extras) { - super(parent); - setLayout(new GridBagLayout()); - JLabel jlInfo = new JLabel("" + R("CVCertificateViewer") + ""); - - @SuppressWarnings("unchecked") LinkedHashMap aliasesMap = (LinkedHashMap)extras[0]; - ArrayList displayedNames = new ArrayList(); - for (Entry entry : aliasesMap.entrySet()) { - String subject = SecurityUtil.getCN(entry.getValue().getSubjectX500Principal().getName()); - String issuer = SecurityUtil.getCN(entry.getValue().getIssuerX500Principal().getName()); - int pos = entry.getKey().lastIndexOf(" (from "); - String source = (pos!=-1)?entry.getKey().substring(pos):""; - displayedNames.add(subject + ":" + issuer + source); - } - - JList jlist = new JList(displayedNames.toArray(new String[0])); - jlist.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - jlist.setSelectedIndex(0); - JScrollPane scrollPane = new JScrollPane(jlist); - - JButton jbOK = new JButton(R("ButOk")); - JButton jbCancel = new JButton(R("ButCancel")); - jbOK.setPreferredSize(jbCancel.getPreferredSize()); - JButton jbDetails = new JButton(R("ButShowDetails")); - jbDetails.setBorderPainted(false); - jbDetails.setContentAreaFilled(false); - jbDetails.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - jbDetails.setMargin(new Insets(0, 0, 0, 0)); - - GridBagConstraints c = new GridBagConstraints(); - c.anchor = GridBagConstraints.NORTHWEST; - c.gridx = 0; - c.gridy = 0; - c.weightx = 1.0; - c.insets = new Insets(10, 5, 3, 3); - add(jlInfo, c); - - c = new GridBagConstraints(); - c.fill = GridBagConstraints.BOTH; - c.gridx = 0; - c.gridy = 1; - c.weightx = 1.0; - c.weighty = 1.0; - c.insets = new Insets(10, 5, 3, 3); - add(scrollPane, c); - - c = new GridBagConstraints(); - c.anchor = GridBagConstraints.SOUTHEAST; - c.gridx = 0; - c.gridy = 2; - c.weightx = 1.0; - c.insets = new Insets(5, 5, 3, 3); - add(jbDetails, c); - - c = new GridBagConstraints(); - c.anchor = GridBagConstraints.SOUTHEAST; - c.gridx = 0; - c.gridy = 3; - c.weightx = 1.0; - c.insets = new Insets(3, 3, 10, 10); - - JPanel okCancelPane = new JPanel(new FlowLayout(FlowLayout.TRAILING, 0, 0)); - okCancelPane.add(jbOK); - okCancelPane.add(Box.createHorizontalStrut(10)); - okCancelPane.add(jbCancel); - add(okCancelPane, c); - - if (parent!=null) { - parent.getViwableDialog().setMinimumSize(new Dimension(500, 300)); - parent.getViwableDialog().setLocationRelativeTo(null); - parent.getViwableDialog().pack(); - } - - initialFocusComponent = scrollPane; - - // click on OK - jbOK.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { - certSelected(parent, jlist); - } - }); - // double-click on selection - jlist.addMouseListener(new MouseAdapter() { - @Override public void mouseClicked(MouseEvent me) { - if (me.getClickCount() == 2) - certSelected(parent, jlist); - } - }); - // enter on selection - jlist.addKeyListener(new KeyAdapter() { - @Override public void keyReleased(KeyEvent ke) { - if(ke.getKeyCode() == KeyEvent.VK_ENTER) - certSelected(parent, jlist); - } - }); - // open certificate details - jbDetails.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { - SecurityDialog.showSingleCertInfoDialog(aliasesMap.values().toArray(new X509Certificate[0])[jlist.getSelectedIndex()], - ClientCertSelectionPane.this); - } - }); - // click on Cancel - jbCancel.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { - parent.setValue(null); - parent.getViwableDialog().dispose(); - } - }); - } - - @Override - public DialogResult getDefaultNegativeAnswer() { - return null; - } - - @Override - public DialogResult getDefaultPositiveAnswer() { - return null; - } - - @Override - public DialogResult readFromStdIn(String what) { - return null; - } - - @Override - public String helpToStdIn() { - return ""; - } - - private void certSelected(SecurityDialog parent, JList jlist) { - parent.setValue(new DialogResult() { - @Override public int getButtonIndex() { - return jlist.getSelectedIndex(); - } - @Override public boolean toBoolean() { - throw new UnsupportedOperationException("Not supported yet."); - } - @Override public String writeValue() { - throw new UnsupportedOperationException("Not supported yet."); - } - }); - parent.getViwableDialog().dispose(); - } - - public static void main(String[] args) throws Exception { - KeyStore ks = KeyStore.getInstance("Windows-MY"); - ks.load(null, null); - Enumerationaliases = ks.aliases(); - LinkedHashMap aliasesMap = new LinkedHashMap(); - while (aliases.hasMoreElements()) { - String alias = aliases.nextElement(); - Certificate c = ks.getCertificate(alias); - if (c instanceof X509Certificate) - aliasesMap.put(alias + " (from browser keystore)", (X509Certificate)c); - } - JFrame f = new JFrame(); - f.setMinimumSize(new Dimension(500, 300)); - f.setSize(700, 300); - f.add(new ClientCertSelectionPane(null, new Object[] { aliasesMap }), BorderLayout.CENTER); - f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - f.pack(); - f.setVisible(true); - } -} \ No newline at end of file + public ClientCertSelectionPane(SecurityDialog parent, Object[] extras) { + super(requireNonNull(parent, "parent")); + setLayout(new GridBagLayout()); + final JLabel jlInfo = new JLabel("" + R("CVCertificateViewer") + ""); + + final Map aliasesMap = getAliases(extras); + final List displayedNames = new ArrayList<>(); + for (final Entry entry : aliasesMap.entrySet()) { + final String subject = SecurityUtil.getCN(entry.getValue().getSubjectX500Principal().getName()); + final String issuer = SecurityUtil.getCN(entry.getValue().getIssuerX500Principal().getName()); + final int pos = entry.getKey().lastIndexOf(" (from "); + final String source = (pos != -1) ? entry.getKey().substring(pos) : ""; + displayedNames.add(subject + ":" + issuer + source); + } + + final JList jList = new JList<>(displayedNames.toArray(new String[0])); + jList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + jList.setSelectedIndex(0); + final JScrollPane scrollPane = new JScrollPane(jList); + + final JButton jbOK = new JButton(R("ButOk")); + final JButton jbCancel = new JButton(R("ButCancel")); + jbOK.setPreferredSize(jbCancel.getPreferredSize()); + final JButton jbDetails = new JButton(R("ButShowDetails")); + jbDetails.setBorderPainted(false); + jbDetails.setContentAreaFilled(false); + jbDetails.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + jbDetails.setMargin(new Insets(0, 0, 0, 0)); + + final GridBagConstraints jlInfoConstraints = new GridBagConstraints(); + jlInfoConstraints.anchor = GridBagConstraints.NORTHWEST; + jlInfoConstraints.gridx = 0; + jlInfoConstraints.gridy = 0; + jlInfoConstraints.weightx = 1.0; + jlInfoConstraints.insets = new Insets(10, 5, 3, 3); + add(jlInfo, jlInfoConstraints); + + final GridBagConstraints scrollPaneConstraints = new GridBagConstraints(); + scrollPaneConstraints.fill = GridBagConstraints.BOTH; + scrollPaneConstraints.gridx = 0; + scrollPaneConstraints.gridy = 1; + scrollPaneConstraints.weightx = 1.0; + scrollPaneConstraints.weighty = 1.0; + scrollPaneConstraints.insets = new Insets(10, 5, 3, 3); + add(scrollPane, scrollPaneConstraints); + + final GridBagConstraints jbDetailsConstraints = new GridBagConstraints(); + jbDetailsConstraints.anchor = GridBagConstraints.SOUTHEAST; + jbDetailsConstraints.gridx = 0; + jbDetailsConstraints.gridy = 2; + jbDetailsConstraints.weightx = 1.0; + jbDetailsConstraints.insets = new Insets(5, 5, 3, 3); + add(jbDetails, jbDetailsConstraints); + + final GridBagConstraints okCancelPaneConstraints = new GridBagConstraints(); + okCancelPaneConstraints.anchor = GridBagConstraints.SOUTHEAST; + okCancelPaneConstraints.gridx = 0; + okCancelPaneConstraints.gridy = 3; + okCancelPaneConstraints.weightx = 1.0; + okCancelPaneConstraints.insets = new Insets(3, 3, 10, 10); + + final JPanel okCancelPane = new JPanel(new FlowLayout(FlowLayout.TRAILING, 0, 0)); + okCancelPane.add(jbOK); + okCancelPane.add(Box.createHorizontalStrut(10)); + okCancelPane.add(jbCancel); + add(okCancelPane, okCancelPaneConstraints); + + parent.getViwableDialog().setMinimumSize(new Dimension(500, 300)); + parent.getViwableDialog().setLocationRelativeTo(null); + parent.getViwableDialog().pack(); + + initialFocusComponent = scrollPane; + + // click on OK + jbOK.addActionListener(new ActionListener() { + @Override + public void actionPerformed(final ActionEvent e) { + ClientCertSelectionPane.this.certSelected(parent, jList); + } + }); + // double-click on selection + jList.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent me) { + if (me.getClickCount() == 2) { + certSelected(parent, jList); + } + } + }); + // enter on selection + jList.addKeyListener(new KeyAdapter() { + @Override + public void keyReleased(KeyEvent ke) { + if (ke.getKeyCode() == KeyEvent.VK_ENTER) { + certSelected(parent, jList); + } + } + }); + // open certificate details + jbDetails.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + SecurityDialog.showSingleCertInfoDialog(aliasesMap.values().toArray(new X509Certificate[0])[jList.getSelectedIndex()], + ClientCertSelectionPane.this); + } + }); + // click on Cancel + jbCancel.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + parent.setValue(null); + parent.getViwableDialog().dispose(); + } + }); + } + + @SuppressWarnings("unchecked") + private Map getAliases(final Object[] extras) { + final Object firstExtra = extras[0]; + if (firstExtra instanceof Map) { + return (Map) firstExtra; + } + return new LinkedHashMap<>(); + } + + @Override + public DialogResult getDefaultNegativeAnswer() { + return null; + } + + @Override + public DialogResult getDefaultPositiveAnswer() { + return null; + } + + @Override + public DialogResult readFromStdIn(String what) { + return null; + } + + @Override + public String helpToStdIn() { + return ""; + } + + private void certSelected(SecurityDialog parent, JList jlist) { + parent.setValue(new DialogResult() { + @Override + public int getButtonIndex() { + return jlist.getSelectedIndex(); + } + + @Override + public boolean toBoolean() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public String writeValue() { + throw new UnsupportedOperationException("Not supported yet."); + } + }); + parent.getViwableDialog().dispose(); + } + + public static void main(String[] args) throws Exception { + final KeyStore ks = KeyStore.getInstance("Windows-MY"); + ks.load(null, null); + final Enumeration aliases = ks.aliases(); + final Map aliasesMap = new LinkedHashMap<>(); + while (aliases.hasMoreElements()) { + final String alias = aliases.nextElement(); + final Certificate c = ks.getCertificate(alias); + if (c instanceof X509Certificate) { + aliasesMap.put(alias + " (from browser keystore)", (X509Certificate) c); + } + } + final JFrame f = new JFrame(); + f.setMinimumSize(new Dimension(500, 300)); + f.setSize(700, 300); + f.add(new ClientCertSelectionPane(null, new Object[]{aliasesMap}), BorderLayout.CENTER); + f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + f.pack(); + f.setVisible(true); + } +} From 7a274477bcf97931bd16084de03b741dac3ca5d0 Mon Sep 17 00:00:00 2001 From: Stephan Classen Date: Wed, 21 Apr 2021 11:03:19 +0200 Subject: [PATCH 3/9] code formatting --- .../dialogs/security/SecurityDialog.java | 142 ++++++------------ ...ViwableDialog.java => ViewableDialog.java} | 106 ++++--------- 2 files changed, 75 insertions(+), 173 deletions(-) rename core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/{ViwableDialog.java => ViewableDialog.java} (66%) diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialog.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialog.java index b6d8d7722..ca3f9e762 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialog.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialog.java @@ -48,7 +48,6 @@ import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dialog.ModalityType; -import java.awt.Window; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.net.URL; @@ -64,7 +63,7 @@ */ public class SecurityDialog { - private final static Logger LOG = LoggerFactory.getLogger(SecurityDialog.class); + private static final Logger LOG = LoggerFactory.getLogger(SecurityDialog.class); /** The type of dialog we want to show */ private final SecurityDialogs.DialogType dialogType; @@ -87,26 +86,27 @@ public class SecurityDialog { */ private final Object[] extras; - /** Whether or not this object has been fully initialized */ - private boolean initialized = false; + private final ViewableDialog viewableDialog; private DialogResult value; - - private ViwableDialog viwableDialog; /** Should show signed JNLP file warning */ private boolean requiresSignedJNLPWarning; + SecurityDialog(SecurityDialogs.DialogType dialogType, AccessType accessType, + JNLPFile file, CertVerifier JarCertVerifier, X509Certificate cert, Object[] extras) { + this(dialogType, accessType, file, JarCertVerifier, cert, extras, false); + } + SecurityDialog(SecurityDialogs.DialogType dialogType, AccessType accessType, JNLPFile file, CertVerifier JarCertVerifier, X509Certificate cert, Object[] extras, boolean showInTaskBar) { - this.viwableDialog = new ViwableDialog(showInTaskBar); + this.viewableDialog = new ViewableDialog(showInTaskBar); this.dialogType = dialogType; this.accessType = accessType; this.file = file; this.certVerifier = JarCertVerifier; this.cert = cert; this.extras = extras; - initialized = true; if(file != null) requiresSignedJNLPWarning= file.requiresSignedJNLPWarning(); @@ -114,19 +114,6 @@ public class SecurityDialog { initDialog(); } - SecurityDialog(SecurityDialogs.DialogType dialogType, AccessType accessType, - JNLPFile file, CertVerifier JarCertVerifier, X509Certificate cert, Object[] extras) { - this(dialogType, accessType, file, JarCertVerifier, cert, extras, false); - } - - /** - * Construct a SecurityDialog to display some sort of access warning - */ - private SecurityDialog(SecurityDialogs.DialogType dialogType, AccessType accessType, - JNLPFile file) { - this(dialogType, accessType, file, null, null, null); - } - /** * Create a SecurityDialog to display a certificate-related warning */ @@ -135,23 +122,6 @@ private SecurityDialog(SecurityDialogs.DialogType dialogType, AccessType accessT this(dialogType, accessType, file, certVerifier, null, null); } - /** - * Create a SecurityDialog to display a certificate-related warning - */ - private SecurityDialog(SecurityDialogs.DialogType dialogType, AccessType accessType, - CertVerifier certVerifier) { - this(dialogType, accessType, null, certVerifier, null, null); - } - - /** - * Create a SecurityDialog to display some sort of access warning - * with more information - */ - private SecurityDialog(SecurityDialogs.DialogType dialogType, AccessType accessType, - JNLPFile file, Object[] extras) { - this(dialogType, accessType, file, null, null, extras); - } - /** * Create a SecurityWarningDialog to display information about a single * certificate @@ -160,14 +130,6 @@ private SecurityDialog(SecurityDialogs.DialogType dialogType, X509Certificate c) this(dialogType, null, null, null, c, null); } - /** - * 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; - } - /** * Shows more information regarding jar code signing * @@ -220,20 +182,15 @@ public static void showSingleCertInfoDialog(X509Certificate c, private void initDialog() { String dialogTitle = createTitle(); - // Note: ViwableDialog methods are deferred until show(): + // Note: ViewableDialog methods are deferred until show(): getViwableDialog().setTitle(dialogTitle); getViwableDialog().setModalityType(ModalityType.MODELESS); getViwableDialog().setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); - // Initialize panel now as its constructor may call getViwableDialog() deferred methods - // to modify dialog state: - SwingUtils.invokeAndWait(new Runnable() { - @Override - public void run() { - installPanel(); - } - }); + // Initialize panel now as its constructor may call getViewableDialog() deferred methods + // to modify dialog state: + SwingUtils.invokeAndWait(this::installPanel); getViwableDialog().pack(); getViwableDialog().centerDialog(); @@ -313,48 +270,47 @@ public X509Certificate getCert() { private SecurityDialogPanel getPanel() { return getPanel(this); } - + /* * find appropriate JPanel to given Dialog, based on {@link DialogType}. */ static SecurityDialogPanel getPanel(SecurityDialog sd) { return getPanel(sd.dialogType, sd); } - + static SecurityDialogPanel getPanel(SecurityDialogs.DialogType type, SecurityDialog sd) { - SecurityDialogPanel lpanel = null; - if (type == SecurityDialogs.DialogType.CERT_WARNING) { - lpanel = new CertWarningPane(sd, sd.certVerifier, (SecurityDelegate) sd.extras[0]); - } else if (type == SecurityDialogs.DialogType.MORE_INFO) { - lpanel = new MoreInfoPane(sd, sd.certVerifier); - } else if (type == SecurityDialogs.DialogType.CERT_INFO) { - lpanel = new CertsInfoPane(sd, sd.certVerifier); - } else if (type == SecurityDialogs.DialogType.SINGLE_CERT_INFO) { - lpanel = new SingleCertInfoPane(sd, sd.certVerifier); - } else if (type == SecurityDialogs.DialogType.ACCESS_WARNING) { - lpanel = new AccessWarningPane(sd, sd.extras, sd.certVerifier); - } else if (type == SecurityDialogs.DialogType.APPLET_WARNING) { - lpanel = new AppletWarningPane(sd, sd.certVerifier); - } else if (type == SecurityDialogs.DialogType.PARTIALLY_SIGNED_WARNING) { - lpanel = AppTrustWarningDialog.partiallySigned(sd, sd.file, (SecurityDelegate) sd.extras[0]); - } else if (type == SecurityDialogs.DialogType.UNSIGNED_WARNING) { - lpanel = AppTrustWarningDialog.unsigned(sd, sd.file); // Only necessary for applets on 'high security' or above - } else if (type == SecurityDialogs.DialogType.AUTHENTICATION) { - lpanel = new PasswordAuthenticationPane(sd, sd.extras); - } else if (type == SecurityDialogs.DialogType.CLIENT_CERT_SELECTION) { - lpanel = new ClientCertSelectionPane(sd, sd.extras); - } else if (type == SecurityDialogs.DialogType.UNSIGNED_EAS_NO_PERMISSIONS_WARNING) { - lpanel = new MissingPermissionsAttributePanel(sd, sd.file.getTitle(), sd.file.getNotNullProbableCodeBase().toExternalForm()); - } else if (type == SecurityDialogs.DialogType.MISSING_ALACA) { - lpanel = new MissingALACAttributePanel(sd, sd.file.getTitle(), (String) sd.extras[0], (String) sd.extras[1]); - } else if (type == SecurityDialogs.DialogType.MATCHING_ALACA) { - lpanel = AppTrustWarningDialog.matchingAlaca(sd, sd.file, (String) sd.extras[0], (String) sd.extras[1]); - } else if (type == SecurityDialogs.DialogType.SECURITY_511) { - lpanel = new InetSecurity511Panel(sd, (URL) sd.extras[0]); - } else { - throw new RuntimeException("Unknown value of " + sd.dialogType + ". Panel will be null. That's not allowed."); + switch (type) { + case CERT_WARNING: + return new CertWarningPane(sd, sd.certVerifier, (SecurityDelegate) sd.extras[0]); + case MORE_INFO: + return new MoreInfoPane(sd, sd.certVerifier); + case CERT_INFO: + return new CertsInfoPane(sd, sd.certVerifier); + case SINGLE_CERT_INFO: + return new SingleCertInfoPane(sd, sd.certVerifier); + case ACCESS_WARNING: + return new AccessWarningPane(sd, sd.extras, sd.certVerifier); + case APPLET_WARNING: + return new AppletWarningPane(sd, sd.certVerifier); + case PARTIALLY_SIGNED_WARNING: + return AppTrustWarningDialog.partiallySigned(sd, sd.file, (SecurityDelegate) sd.extras[0]); + case UNSIGNED_WARNING: + return AppTrustWarningDialog.unsigned(sd, sd.file); + case AUTHENTICATION: + return new PasswordAuthenticationPane(sd, sd.extras); + case CLIENT_CERT_SELECTION: + return new ClientCertSelectionPane(sd, sd.extras); + case UNSIGNED_EAS_NO_PERMISSIONS_WARNING: + return new MissingPermissionsAttributePanel(sd, sd.file.getTitle(), sd.file.getNotNullProbableCodeBase().toExternalForm()); + case MISSING_ALACA: + return new MissingALACAttributePanel(sd, sd.file.getTitle(), (String) sd.extras[0], (String) sd.extras[1]); + case MATCHING_ALACA: + return AppTrustWarningDialog.matchingAlaca(sd, sd.file, (String) sd.extras[0], (String) sd.extras[1]); + case SECURITY_511: + return new InetSecurity511Panel(sd, (URL) sd.extras[0]); + default: + throw new RuntimeException("Unknown value of " + sd.dialogType + ". Panel will be null. That's not allowed."); } - return lpanel; } /* @@ -383,7 +339,7 @@ public DialogResult getValue() { return value; } - + public boolean requiresSignedJNLPWarning() { return requiresSignedJNLPWarning; @@ -409,10 +365,10 @@ String helpToStdIn(){ return panel.helpToStdIn(); } - public ViwableDialog getViwableDialog() { - return viwableDialog; + public ViewableDialog getViwableDialog() { + return viewableDialog; } - + public SecurityDialogPanel getSecurityDialogPanel(){ return panel; } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ViwableDialog.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ViewableDialog.java similarity index 66% rename from core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ViwableDialog.java rename to core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ViewableDialog.java index 0240bcdb4..d35d13bd5 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ViwableDialog.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ViewableDialog.java @@ -48,72 +48,54 @@ import java.util.concurrent.CopyOnWriteArrayList; /** - * This class encapsulate viwable part of SecurityDialog, so it do not need to + * This class encapsulate viewable part of SecurityDialog, so it do not need to * extend it. - * + *

* It is accepting commons setters for jdialog, but actually applying them right before it is created. * Obviously it do not have getters, but jdialog itself should not be keeper of any information. SecurityPanel is. */ -public class ViwableDialog { +public class ViewableDialog { + + private final List operations = new ArrayList<>(); + private final boolean showInTaskBar; private JDialog jd = null; - private boolean showInTaskBar; - List operations = new ArrayList(); - public ViwableDialog(boolean showInTaskBar) { + public ViewableDialog(boolean showInTaskBar) { this.showInTaskBar = showInTaskBar; } private JDialog createJDialog() { - jd = showInTaskBar?new JDialog((Dialog)null):new JDialog(); + jd = showInTaskBar ? new JDialog((Dialog) null) : new JDialog(); jd.setName("ViwableDialog"); SwingUtils.info(jd); jd.setIconImages(ImageResources.INSTANCE.getApplicationImages()); - - for (Runnable operation : operations) { + + for (final Runnable operation : operations) { operation.run(); } - // prune operations. May throw NPE if operations used after createJDialog() - operations = null; + operations.clear(); return jd; } public void setMinimumSize(final Dimension minimumSize) { - operations.add(new Runnable() { - @Override - public void run() { - jd.setMinimumSize(minimumSize); - } - }); + operations.add(() -> jd.setMinimumSize(minimumSize)); } public void pack() { - operations.add(new Runnable() { - @Override - public void run() { - jd.pack(); - } - }); + operations.add(() -> jd.pack()); } public void setLocationRelativeTo(final Component c) { - operations.add(new Runnable() { - @Override - public void run() { - jd.setLocationRelativeTo(c); - } - }); + operations.add(() -> jd.setLocationRelativeTo(c)); } public void show() { - SwingUtils.invokeAndWait(new Runnable() { - @Override - public void run() { - if (jd == null) { - jd = createJDialog(); - } - jd.setVisible(true); + SwingUtils.invokeAndWait(() -> { + if (jd == null) { + jd = createJDialog(); } + jd.setVisible(true); }); } @@ -122,7 +104,7 @@ public void run() { * choice (Ok, Cancel, etc) or closed the window */ public void dispose() { - // avoid reentrance: + // avoid re-entrance: if (jd != null) { notifySelectionMade(); @@ -156,39 +138,19 @@ public void addActionListener(ActionListener listener) { } public void add(final SecurityDialogPanel panel, final String constraints) { - operations.add(new Runnable() { - @Override - public void run() { - jd.add(panel, constraints); - } - }); + operations.add(() -> jd.add(panel, constraints)); } public void setModalityType(final Dialog.ModalityType modalityType) { - operations.add(new Runnable() { - @Override - public void run() { - jd.setModalityType(modalityType); - } - }); + operations.add(() -> jd.setModalityType(modalityType)); } public void setTitle(final String title) { - operations.add(new Runnable() { - @Override - public void run() { - jd.setTitle(title); - } - }); + operations.add(() -> jd.setTitle(title)); } public void setDefaultCloseOperation(final int op) { - operations.add(new Runnable() { - @Override - public void run() { - jd.setDefaultCloseOperation(op); - } - }); + operations.add(() -> jd.setDefaultCloseOperation(op)); } private static void centerDialog(JDialog dialog) { @@ -196,12 +158,7 @@ private static void centerDialog(JDialog dialog) { } public void centerDialog() { - operations.add(new Runnable() { - @Override - public void run() { - centerDialog(jd); - } - }); + operations.add(() -> centerDialog(jd)); } public void setResizable(final boolean b) { @@ -212,21 +169,10 @@ public void setResizable(final boolean b) { } public void addWindowListener(final WindowAdapter adapter) { - operations.add(new Runnable() { - @Override - public void run() { - jd.addWindowListener(adapter); - } - }); + operations.add(() -> jd.addWindowListener(adapter)); } public void addWindowFocusListener(final WindowAdapter adapter) { - operations.add(new Runnable() { - @Override - public void run() { - jd.addWindowFocusListener(adapter); - } - }); + operations.add(() -> jd.addWindowFocusListener(adapter)); } - } From d3aec2c18f2781d86c643318ccf77d0f8716608d Mon Sep 17 00:00:00 2001 From: Stephan Classen Date: Wed, 21 Apr 2021 11:17:37 +0200 Subject: [PATCH 4/9] code formatting --- .../dialogs/security/SecurityDialogs.java | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogs.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogs.java index ec421769e..62e15cb06 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogs.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogs.java @@ -52,7 +52,6 @@ import javax.swing.JDialog; -import java.awt.Dialog; import java.awt.Dialog.ModalityType; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; @@ -62,6 +61,7 @@ import java.security.PrivilegedAction; import java.security.cert.X509Certificate; import java.util.LinkedHashMap; +import java.util.Objects; import java.util.Set; import java.util.concurrent.Semaphore; @@ -80,12 +80,12 @@ */ public class SecurityDialogs { - private final static Logger LOG = LoggerFactory.getLogger(SecurityDialogs.class); + private static final Logger LOG = LoggerFactory.getLogger(SecurityDialogs.class); /** * Types of dialogs we can create */ - public static enum DialogType { + public enum DialogType { CERT_WARNING, MORE_INFO, @@ -112,9 +112,10 @@ public static enum DialogType { * @param extras array of objects used as extra.toString or similarly later * @return true if permission was granted by the user, false otherwise. */ - public static AccessWarningPaneComplexReturn showAccessWarningDialog(final AccessType accessType, - final JNLPFile file, final Object[] extras) { - + public static AccessWarningPaneComplexReturn showAccessWarningDialog( + final AccessType accessType, + final JNLPFile file, final Object[] extras + ) { final SecurityDialogMessage message = new SecurityDialogMessage(file); message.dialogType = DialogType.ACCESS_WARNING; @@ -138,7 +139,7 @@ public static YesNoSandboxLimited showUnsignedWarningDialog(JNLPFile file) { message.dialogType = DialogType.UNSIGNED_WARNING; message.accessType = AccessType.UNSIGNED; - DialogResult r = getUserResponse(message); + final DialogResult r = getUserResponse(message); return (YesNoSandboxLimited) r; } @@ -158,16 +159,18 @@ public static YesNoSandboxLimited showUnsignedWarningDialog(JNLPFile file) { * wants the applet to run with only sandbox permissions, or CANCEL if the * user did not accept running the applet */ - public static YesNoSandbox showCertWarningDialog(AccessType accessType, - JNLPFile file, CertVerifier certVerifier, SecurityDelegate securityDelegate) { - + public static YesNoSandbox showCertWarningDialog( + AccessType accessType, + JNLPFile file, + CertVerifier certVerifier, SecurityDelegate securityDelegate + ) { final SecurityDialogMessage message = new SecurityDialogMessage(file); message.dialogType = DialogType.CERT_WARNING; message.accessType = accessType; message.certVerifier = certVerifier; message.extras = new Object[]{securityDelegate}; - DialogResult selectedValue = getUserResponse(message); + final DialogResult selectedValue = getUserResponse(message); return (YesNoSandbox) selectedValue; } @@ -181,16 +184,18 @@ public static YesNoSandbox showCertWarningDialog(AccessType accessType, * @param securityDelegate the delegate for security atts. * @return true if permission was granted by the user, false otherwise. */ - public static YesNoSandbox showPartiallySignedWarningDialog(JNLPFile file, CertVerifier certVerifier, - SecurityDelegate securityDelegate) { - + public static YesNoSandbox showPartiallySignedWarningDialog( + JNLPFile file, + CertVerifier certVerifier, + SecurityDelegate securityDelegate + ) { final SecurityDialogMessage message = new SecurityDialogMessage(file); message.dialogType = DialogType.PARTIALLY_SIGNED_WARNING; message.accessType = AccessType.PARTIALLY_SIGNED; message.certVerifier = certVerifier; message.extras = new Object[]{securityDelegate}; - DialogResult r = getUserResponse(message); + final DialogResult r = getUserResponse(message); return (YesNoSandbox) r; } @@ -209,10 +214,9 @@ public static YesNoSandbox showPartiallySignedWarningDialog(JNLPFile file, CertV */ public static NamePassword showAuthenticationPrompt(String host, int port, String prompt, String type) { - SecurityManager sm = System.getSecurityManager(); + final SecurityManager sm = System.getSecurityManager(); if (sm != null) { - NetPermission requestPermission - = new NetPermission("requestPasswordAuthentication"); + final NetPermission requestPermission = new NetPermission("requestPasswordAuthentication"); sm.checkPermission(requestPermission); } @@ -221,11 +225,11 @@ public static NamePassword showAuthenticationPrompt(String host, int port, Strin message.dialogType = DialogType.AUTHENTICATION; message.extras = new Object[]{host, port, prompt, type}; - DialogResult response = getUserResponse(message); - LOG.debug("Decided action for matching alaca at was {}", response); + final DialogResult response = getUserResponse(message); + LOG.debug("Decided action for matching alaca at was {}", response); return (NamePassword) response; } - + public static DialogResult showClientCertSelectionPrompt(LinkedHashMap aliases) { final SecurityDialogMessage message = new SecurityDialogMessage(null); @@ -233,23 +237,24 @@ public static DialogResult showClientCertSelectionPrompt(LinkedHashMap remoteUrls) { - SecurityDialogMessage message = new SecurityDialogMessage(file); + final SecurityDialogMessage message = new SecurityDialogMessage(file); message.dialogType = DialogType.MISSING_ALACA; - String urlToShow = file.getNotNullProbableCodeBase().toExternalForm(); + final String urlToShow; if (codeBase != null) { urlToShow = codeBase.toString(); } else { + urlToShow = file.getNotNullProbableCodeBase().toExternalForm(); LOG.warn("Warning, null codebase wants to show in ALACA!"); } message.extras = new Object[]{urlToShow, UrlUtils.setOfUrlsToHtmlList(remoteUrls)}; - DialogResult selectedValue = getUserResponse(message); + final DialogResult selectedValue = getUserResponse(message); LOG.debug("Decided action for matching alaca at {} was {}", file.getCodeBase(), selectedValue); @@ -261,14 +266,16 @@ public static boolean showMissingALACAttributePanel(JNLPFile file, URL codeBase, public static boolean showMatchingALACAttributePanel(JNLPFile file, URL documentBase, Set remoteUrls) { - SecurityDialogMessage message = new SecurityDialogMessage(file); + final SecurityDialogMessage message = new SecurityDialogMessage(file); message.dialogType = DialogType.MATCHING_ALACA; - String docBaseString = "null-documentbase"; + final String docBaseString; if (documentBase != null) { docBaseString = documentBase.toString(); + } else { + docBaseString = "null-documentbase"; } message.extras = new Object[]{docBaseString, UrlUtils.setOfUrlsToHtmlList(remoteUrls)}; - DialogResult selectedValue = getUserResponse(message); + final DialogResult selectedValue = getUserResponse(message); LOG.debug("Decided action for matching alaca at {} was {}", file.getCodeBase(), selectedValue); @@ -277,14 +284,13 @@ public static boolean showMatchingALACAttributePanel(JNLPFile file, URL document } return false; - } public static boolean showMissingPermissionsAttributeDialogue(JNLPFile file) { - SecurityDialogMessage message = new SecurityDialogMessage(file); + final SecurityDialogMessage message = new SecurityDialogMessage(file); message.dialogType = DialogType.UNSIGNED_EAS_NO_PERMISSIONS_WARNING; - DialogResult selectedValue = getUserResponse(message); + final DialogResult selectedValue = getUserResponse(message); LOG.debug("Decided action for missing permissions at {} was {}", file.getCodeBase(), selectedValue); if (selectedValue != null) { @@ -339,12 +345,9 @@ private static DialogResult getUserResponse(final SecurityDialogMessage message) public void windowOpened(WindowEvent e) { message.toDispose = fakeDialog; message.lock = null; - AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Void run() { - JNLPRuntime.getSecurityDialogHandler().postMessage(message); - return null; - } + AccessController.doPrivileged((PrivilegedAction) () -> { + JNLPRuntime.getSecurityDialogHandler().postMessage(message); + return null; }); } }); @@ -378,14 +381,11 @@ public Void run() { // false = terminate ITW // true = continue public static boolean show511Dialogue(Resource r) { - SecurityDialogMessage message = new SecurityDialogMessage(null); + final SecurityDialogMessage message = new SecurityDialogMessage(null); message.dialogType = DialogType.SECURITY_511; message.extras = new Object[]{r.getLocation()}; - DialogResult selectedValue = getUserResponse(message); - if (selectedValue != null && selectedValue.equals(YesCancel.cancel())) { - return false; //kill command - } - return true; + final DialogResult selectedValue = getUserResponse(message); + return !Objects.equals(selectedValue, YesCancel.cancel()); } } From cb69084ac806d4588dc454b0f575ddf456762a65 Mon Sep 17 00:00:00 2001 From: Stephan Classen Date: Wed, 21 Apr 2021 11:21:32 +0200 Subject: [PATCH 5/9] use Map instead of LinkedHashMap --- .../client/parts/dialogs/security/SecurityDialogs.java | 4 ++-- .../java/net/sourceforge/jnlp/runtime/MergedKeyManager.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogs.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogs.java index 62e15cb06..c7b7e1990 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogs.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogs.java @@ -60,7 +60,7 @@ import java.security.AccessController; import java.security.PrivilegedAction; import java.security.cert.X509Certificate; -import java.util.LinkedHashMap; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.Semaphore; @@ -230,7 +230,7 @@ public static NamePassword showAuthenticationPrompt(String host, int port, Strin return (NamePassword) response; } - public static DialogResult showClientCertSelectionPrompt(LinkedHashMap aliases) { + public static DialogResult showClientCertSelectionPrompt(Map aliases) { final SecurityDialogMessage message = new SecurityDialogMessage(null); message.dialogType = DialogType.CLIENT_CERT_SELECTION; diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/MergedKeyManager.java b/core/src/main/java/net/sourceforge/jnlp/runtime/MergedKeyManager.java index 17e386b42..976f7b171 100644 --- a/core/src/main/java/net/sourceforge/jnlp/runtime/MergedKeyManager.java +++ b/core/src/main/java/net/sourceforge/jnlp/runtime/MergedKeyManager.java @@ -165,8 +165,8 @@ private String getHostFromSocket(Socket socket) { } return null; } - - private String getPreferredAlias(LinkedHashMap aliasesMap, String aliasType) { + + private String getPreferredAlias(Map aliasesMap, String aliasType) { String alias = null; if (aliasesMap.size() > 1) { if (JNLPRuntime.isHeadless()) { From c1b6f68475ea6db16be9783f9cad32788bc22750 Mon Sep 17 00:00:00 2001 From: Stephan Classen Date: Wed, 21 Apr 2021 14:07:56 +0200 Subject: [PATCH 6/9] code formatting --- .../icedteaweb/resources/ResourceTracker.java | 7 ++++--- .../java/net/sourceforge/jnlp/cache/CacheUtil.java | 12 +++++++----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/ResourceTracker.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/ResourceTracker.java index ceb8f85cc..d2b06e776 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/ResourceTracker.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/resources/ResourceTracker.java @@ -384,7 +384,7 @@ private void waitForCompletion(Resource... resources) { final int threadCount = Math.min(configuredThreadCount, resources.length); final ExecutorService downloadExecutor = Executors.newFixedThreadPool(threadCount, new DaemonThreadFactory()); try { - final List> futures = Arrays.asList(resources).stream() + final List> futures = Arrays.stream(resources) .map(r -> triggerDownloadFor(r, downloadExecutor)) .collect(Collectors.toList()); @@ -397,9 +397,10 @@ private void waitForCompletion(Resource... resources) { LOG.debug("Download done. Shutting down executor"); downloadExecutor.shutdownNow(); } - - if (resources.length == 1 && resources[0].isSet(Status.ERROR)) + + if (resources.length == 1 && resources[0].isSet(Status.ERROR)) { throw new RuntimeException("Error while downloading initial resource " + resources[0]); + } } private Future triggerDownloadFor(Resource resource, final Executor downloadExecutor) { diff --git a/core/src/main/java/net/sourceforge/jnlp/cache/CacheUtil.java b/core/src/main/java/net/sourceforge/jnlp/cache/CacheUtil.java index 691f1f92e..4698734d8 100644 --- a/core/src/main/java/net/sourceforge/jnlp/cache/CacheUtil.java +++ b/core/src/main/java/net/sourceforge/jnlp/cache/CacheUtil.java @@ -234,18 +234,19 @@ public static String hex(String origName, String candidate) throws NoSuchAlgorit * @param title name of the download */ public static void waitForResources(final JNLPClassLoader jnlpClassLoader, final ResourceTracker tracker, final URL[] resources, final String title) { - // download first initial jar : so in case of client certificate, only 1 https client handshake is done + // download first initial jar : so in case of client certificate, only 1 https client handshake is done boolean downloadInitialJarFirst = resources.length > 1 && resources[0].getProtocol().equals("https"); try { final DownloadIndicator indicator = Optional.ofNullable(JNLPRuntime.getDefaultDownloadIndicator()) - .orElseGet(() -> new DummyDownloadIndicator()); + .orElseGet(DummyDownloadIndicator::new); final DownloadServiceListener listener = getDownloadServiceListener(jnlpClassLoader, title, resources, indicator); try { for (URL url : resources) { tracker.addDownloadListener(url, resources, listener); } - if (downloadInitialJarFirst) + if (downloadInitialJarFirst) { tracker.waitForResources(resources[0]); + } // download all remaining ones tracker.waitForResources(resources); } finally { @@ -253,8 +254,9 @@ public static void waitForResources(final JNLPClassLoader jnlpClassLoader, final } } catch (Exception ex) { LOG.error("Downloading of resources ended with error", ex); - if (downloadInitialJarFirst) - throw new RuntimeException(ex); + if (downloadInitialJarFirst) { + throw new RuntimeException(ex); + } } } From d2023215a1933a1a04a62b5663cf523ce6a06638 Mon Sep 17 00:00:00 2001 From: Stephan Classen Date: Wed, 21 Apr 2021 14:36:44 +0200 Subject: [PATCH 7/9] code formatting --- .../jnlp/runtime/MergedKeyManager.java | 515 +++++++++--------- 1 file changed, 272 insertions(+), 243 deletions(-) diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/MergedKeyManager.java b/core/src/main/java/net/sourceforge/jnlp/runtime/MergedKeyManager.java index 976f7b171..6ddc163f4 100644 --- a/core/src/main/java/net/sourceforge/jnlp/runtime/MergedKeyManager.java +++ b/core/src/main/java/net/sourceforge/jnlp/runtime/MergedKeyManager.java @@ -1,5 +1,17 @@ package net.sourceforge.jnlp.runtime; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialogs; +import net.adoptopenjdk.icedteaweb.logging.Logger; +import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.DialogResult; +import net.sourceforge.jnlp.security.SecurityUtil; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.X509ExtendedKeyManager; +import javax.net.ssl.X509KeyManager; import java.lang.reflect.Method; import java.net.Socket; import java.security.KeyStore; @@ -15,273 +27,290 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.X509ExtendedKeyManager; -import javax.net.ssl.X509KeyManager; +import static net.sourceforge.jnlp.security.KeyStores.Level.SYSTEM; +import static net.sourceforge.jnlp.security.KeyStores.Level.USER; +import static net.sourceforge.jnlp.security.KeyStores.Type.CLIENT_CERTS; +import static net.sourceforge.jnlp.security.KeyStores.getKeyStore; -import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialogs; -import net.adoptopenjdk.icedteaweb.logging.Logger; -import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; -import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.DialogResult; -import net.sourceforge.jnlp.security.KeyStores; -import net.sourceforge.jnlp.security.SecurityUtil; +public class MergedKeyManager extends X509ExtendedKeyManager { + private static final Logger LOG = LoggerFactory.getLogger(MergedKeyManager.class); + private static final Map hostToClientAliasCache = new ConcurrentHashMap<>(); + private final X509KeyManager browserKeyManager; + private final X509KeyManager userKeyManager; + private final X509KeyManager systemKeyManager; + private static final String USER_SUFFIX = " (from user keystore)"; + private static final String SYSTEM_SUFFIX = " (from system keystore)"; + private static final String BROWSER_SUFFIX = " (from browser keystore)"; + private static boolean userCancelled = false; -public class MergedKeyManager extends X509ExtendedKeyManager -{ - private final static Logger LOG = LoggerFactory.getLogger(MergedKeyManager.class); - private static Map hostToClientAliasCache = new ConcurrentHashMap(); - private X509KeyManager browserKeyManager; - private X509KeyManager userKeyManager; - private X509KeyManager systemKeyManager; - private final static String USER_SUFFIX = " (from user keystore)"; - private final static String SYSTEM_SUFFIX = " (from system keystore)"; - private final static String BROWSER_SUFFIX = " (from browser keystore)"; - private static boolean userCancelled = false; - - MergedKeyManager() { - userKeyManager = getKeyManager(KeyStores.getKeyStore(KeyStores.Level.USER, KeyStores.Type.CLIENT_CERTS).getKs(), "SunX509"); - systemKeyManager = getKeyManager(KeyStores.getKeyStore(KeyStores.Level.SYSTEM, KeyStores.Type.CLIENT_CERTS).getKs(), "SunX509"); - if (System.getProperty("os.name").startsWith("Windows")) { - try { - KeyStore ks = KeyStore.getInstance("Windows-MY"); - ks.load(null, null); - browserKeyManager = getKeyManager(ks, "SunX509"); - } - catch (Exception e) { - LOG.error("Unable to get browser keystore information", e); - } - } - } - - private X509KeyManager getKeyManager(KeyStore ks, String algo) { - try { - KeyManagerFactory kmf = KeyManagerFactory.getInstance(algo); - SecurityUtil.initKeyManagerFactory(kmf, ks); - KeyManager[] keyManagers = kmf.getKeyManagers(); - if (keyManagers != null) - for (KeyManager keyManager : keyManagers) - if (keyManager instanceof X509KeyManager) - return (X509KeyManager)keyManager; - } - catch (Exception e) { - LOG.warn("Unable to get KeyStore " + ks, e); + MergedKeyManager() { + userKeyManager = getKeyManager(getKeyStore(USER, CLIENT_CERTS).getKs()); + systemKeyManager = getKeyManager(getKeyStore(SYSTEM, CLIENT_CERTS).getKs()); + browserKeyManager = getX509KeyManager(); } - return null; - } - @Override public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) { - if (userCancelled) { - LOG.warn("Client certificate selection previously cancelled by user, returning null alias"); - return null; + private X509KeyManager getX509KeyManager() { + if (System.getProperty("os.name").startsWith("Windows")) { + try { + final KeyStore ks = KeyStore.getInstance("Windows-MY"); + ks.load(null, null); + return getKeyManager(ks); + } catch (Exception e) { + LOG.error("Unable to get browser keystore information", e); + } + } + return null; } - - String host = getHostFromSocket(socket); - LOG.info("Retrieved host from socket : " + host); - if (host != null) { - String alias = hostToClientAliasCache.get(host.toLowerCase()); - LOG.info("Found " + alias + " alias in cache for " + host.toLowerCase() + " and keyTypes " + Arrays.toString(keyTypes)); - if (alias != null) - return alias; + + private X509KeyManager getKeyManager(KeyStore ks) { + try { + final KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + SecurityUtil.initKeyManagerFactory(kmf, ks); + final KeyManager[] keyManagers = kmf.getKeyManagers(); + if (keyManagers != null) { + for (KeyManager keyManager : keyManagers) { + if (keyManager instanceof X509KeyManager) { + return (X509KeyManager) keyManager; + } + } + } + } catch (Exception e) { + LOG.warn("Unable to get KeyStore " + ks, e); + } + return null; } - - LinkedHashMap validClientAliases = new LinkedHashMap<>(); - LinkedHashMap expiredClientAliases = new LinkedHashMap<>(); - LinkedHashMap otherAliases = new LinkedHashMap<>(); - for (String keyType : keyTypes) { - String[] aliasesWithSuffix = getClientAliases(keyType, issuers); - //if (aliasesWithSuffix != null) - for (String aliasWithSuffix : aliasesWithSuffix) - { - X509Certificate[] certs = getCertificateChain(removeAliasSuffix(aliasWithSuffix)); - if (certs == null || certs.length == 0) - continue; - X509Certificate cert = certs[0]; - try { - List usage = cert.getExtendedKeyUsage(); - // Extensions : 1.3.6.1.5.5.7.3.2=ClientCert 2.5.29.37.0=ANY - if (usage != null && (usage.contains("1.3.6.1.5.5.7.3.2") || usage.contains("2.5.29.37.0"))) { - try { - cert.checkValidity(); - LOG.info("Found valid client alias with clientCert or ANY extension: " + aliasWithSuffix); - validClientAliases.put(aliasWithSuffix, cert); - } - catch (CertificateException e) { - LOG.warn("Found expired client alias with clientCert or ANY extension: " + aliasWithSuffix); - expiredClientAliases.put(aliasWithSuffix, cert); - } + + @Override + public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) { + if (userCancelled) { + LOG.warn("Client certificate selection previously cancelled by user, returning null alias"); + return null; + } + + final String host = getHostFromSocket(socket); + LOG.info("Retrieved host from socket : " + host); + if (host != null) { + final String alias = hostToClientAliasCache.get(host.toLowerCase()); + LOG.info("Found " + alias + " alias in cache for " + host.toLowerCase() + " and keyTypes " + Arrays.toString(keyTypes)); + if (alias != null) { + return alias; } - else { - LOG.warn("Found non-client alias: " + aliasWithSuffix); - otherAliases.put(aliasWithSuffix, cert); + } + + final Map validClientAliases = new LinkedHashMap<>(); + final Map expiredClientAliases = new LinkedHashMap<>(); + final Map otherAliases = new LinkedHashMap<>(); + for (final String keyType : keyTypes) { + final String[] aliasesWithSuffix = getClientAliases(keyType, issuers); + //if (aliasesWithSuffix != null) + for (final String aliasWithSuffix : aliasesWithSuffix) { + X509Certificate[] certs = getCertificateChain(removeAliasSuffix(aliasWithSuffix)); + if (certs == null || certs.length == 0) { + continue; + } + final X509Certificate cert = certs[0]; + try { + final List usage = cert.getExtendedKeyUsage(); + // Extensions : 1.3.6.1.5.5.7.3.2=ClientCert 2.5.29.37.0=ANY + if (usage != null && (usage.contains("1.3.6.1.5.5.7.3.2") || usage.contains("2.5.29.37.0"))) { + try { + cert.checkValidity(); + LOG.info("Found valid client alias with clientCert or ANY extension: " + aliasWithSuffix); + validClientAliases.put(aliasWithSuffix, cert); + } catch (CertificateException e) { + LOG.warn("Found expired client alias with clientCert or ANY extension: " + aliasWithSuffix); + expiredClientAliases.put(aliasWithSuffix, cert); + } + } else { + LOG.warn("Found non-client alias: " + aliasWithSuffix); + otherAliases.put(aliasWithSuffix, cert); + } + } catch (CertificateParsingException e) { + LOG.warn("Exception while getting ExtendedKeyUsage for alias " + aliasWithSuffix); + otherAliases.put(aliasWithSuffix, cert); + } } - } - catch (CertificateParsingException e) { - LOG.warn("Exception while getting ExtendedKeyUsage for alias " + aliasWithSuffix); - otherAliases.put(aliasWithSuffix, cert); - } - } + } + + final String alias; + if (!validClientAliases.isEmpty()) { + alias = getPreferredAlias(validClientAliases, "valid client"); + } else { + expiredClientAliases.putAll(otherAliases); + if (!expiredClientAliases.isEmpty()) { + alias = getPreferredAlias(expiredClientAliases, "remaining"); + } else { + LOG.warn("Could not find any client alias for keyTypes " + Arrays.toString(keyTypes)); + alias = null; + } + } + + if (socket instanceof SSLSocket && host != null && alias != null) { + LOG.info("Added " + alias + " alias in cache for " + host.toLowerCase()); + hostToClientAliasCache.put(host.toLowerCase(), alias); + } + return alias; } - - String alias = null; - if (!validClientAliases.isEmpty()) - alias = getPreferredAlias(validClientAliases, "valid client"); - else { - expiredClientAliases.putAll(otherAliases); - if (!expiredClientAliases.isEmpty()) - alias = getPreferredAlias(expiredClientAliases, "remaining"); - else - LOG.warn("Could not find any client alias for keyTypes " + Arrays.toString(keyTypes)); + + @SuppressWarnings({"rawtypes", "unchecked"}) + private String getHostFromSocket(Socket socket) { + try { + final Class c = Class.forName("sun.security.ssl.SSLSocketImpl"); + if (c.isInstance(socket)) { + final Object o = c.cast(socket); + Method m; + try { + m = c.getDeclaredMethod("getHost"); + m.setAccessible(true); + } catch (NoSuchMethodException e) { + m = c.getDeclaredMethod("getPeerHost"); + } + return (String) m.invoke(o); + } + } catch (Exception e) { + LOG.warn("Cannot get remote host from Socket", e); + } + return null; } - - if (socket instanceof SSLSocket && host != null && alias != null) { - LOG.info("Added " + alias + " alias in cache for " + host.toLowerCase()); - hostToClientAliasCache.put(host.toLowerCase(), alias); + + private String getPreferredAlias(Map aliasesMap, String aliasType) { + final String alias; + if (aliasesMap.size() > 1) { + if (JNLPRuntime.isHeadless()) { + alias = aliasesMap.keySet().iterator().next(); + LOG.info("Returning the first " + aliasType + " alias in headless mode : " + alias); + } else { + final DialogResult res = SecurityDialogs.showClientCertSelectionPrompt(aliasesMap); + if (res == null) { + alias = null; + userCancelled = true; + LOG.warn("Client certificate selection cancelled by user"); + } else { + alias = aliasesMap.keySet().toArray(new String[0])[res.getButtonIndex()]; + LOG.info("Returning the selected " + aliasType + " alias : " + alias); + } + } + } else if (aliasesMap.size() == 1) { + alias = aliasesMap.keySet().iterator().next(); + LOG.info("Returning the only " + aliasType + " alias : " + alias); + } else { + alias = null; + } + return removeAliasSuffix(alias); } - return alias; - } - private String getHostFromSocket(Socket socket) { - try { - Class c = Class.forName("sun.security.ssl.SSLSocketImpl"); - if (c.isInstance(socket)) { - Object o = c.cast(socket); - Method m = null; - try { - m = c.getDeclaredMethod("getHost", null); - m.setAccessible(true); + private String removeAliasSuffix(String aliasWithSuffix) { + if (aliasWithSuffix == null) { + return null; + } + if (aliasWithSuffix.endsWith(USER_SUFFIX)) { + return aliasWithSuffix.substring(0, aliasWithSuffix.length() - USER_SUFFIX.length()); + } + if (aliasWithSuffix.endsWith(SYSTEM_SUFFIX)) { + return aliasWithSuffix.substring(0, aliasWithSuffix.length() - SYSTEM_SUFFIX.length()); } - catch (NoSuchMethodException e) { - m = c.getDeclaredMethod("getPeerHost", null); + if (aliasWithSuffix.endsWith(BROWSER_SUFFIX)) { + return aliasWithSuffix.substring(0, aliasWithSuffix.length() - BROWSER_SUFFIX.length()); } - if (m != null) - return (String)m.invoke(o, null); - } + return aliasWithSuffix; } - catch (Exception e) { - LOG.warn("Cannot get remote host from Socket", e); + + @Override + public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { + String alias = null; + if (userKeyManager != null) { + alias = userKeyManager.chooseServerAlias(keyType, issuers, socket); + } + if (alias == null && systemKeyManager != null) { + alias = systemKeyManager.chooseServerAlias(keyType, issuers, socket); + } + if (alias == null && browserKeyManager != null) { + alias = browserKeyManager.chooseServerAlias(keyType, issuers, socket); + } + return alias; } - return null; - } - private String getPreferredAlias(Map aliasesMap, String aliasType) { - String alias = null; - if (aliasesMap.size() > 1) { - if (JNLPRuntime.isHeadless()) { - alias = aliasesMap.keySet().iterator().next(); - LOG.info("Returning the first " + aliasType + " alias in headless mode : " + alias); - } - else { - DialogResult res = SecurityDialogs.showClientCertSelectionPrompt(aliasesMap); - if (res == null) { - userCancelled = true; - LOG.warn("Client certificate selection cancelled by user"); + @Override + public X509Certificate[] getCertificateChain(String alias) { + X509Certificate[] x509certificates = null; + if (userKeyManager != null) { + x509certificates = userKeyManager.getCertificateChain(alias); + } + if (x509certificates == null && systemKeyManager != null) { + x509certificates = systemKeyManager.getCertificateChain(alias); } - else { - alias = aliasesMap.keySet().toArray(new String[0])[res.getButtonIndex()]; - LOG.info("Returning the selected " + aliasType + " alias : " + alias); + if (x509certificates == null && browserKeyManager != null) { + x509certificates = browserKeyManager.getCertificateChain(alias); } - } + return x509certificates; } - else if (aliasesMap.size() == 1) { - alias = aliasesMap.keySet().iterator().next(); - LOG.info("Returning the only " + aliasType + " alias : " + alias); + + @Override + public String[] getClientAliases(String keyType, Principal[] issuers) { + final List aliases = new ArrayList<>(); + if (userKeyManager != null) { + addNonNullStrings(aliases, userKeyManager.getClientAliases(keyType, issuers), USER_SUFFIX); + } + if (systemKeyManager != null) { + addNonNullStrings(aliases, systemKeyManager.getClientAliases(keyType, issuers), SYSTEM_SUFFIX); + } + if (browserKeyManager != null) { + addNonNullStrings(aliases, browserKeyManager.getClientAliases(keyType, issuers), BROWSER_SUFFIX); + } + return aliases.toArray(new String[0]); } - return removeAliasSuffix(alias); - } - - private String removeAliasSuffix(String aliasWithSuffix) { - if (aliasWithSuffix.endsWith(USER_SUFFIX)) - return aliasWithSuffix.substring(0, aliasWithSuffix.length() - USER_SUFFIX.length()); - if (aliasWithSuffix.endsWith(SYSTEM_SUFFIX)) - return aliasWithSuffix.substring(0, aliasWithSuffix.length() - SYSTEM_SUFFIX.length()); - if (aliasWithSuffix.endsWith(BROWSER_SUFFIX)) - return aliasWithSuffix.substring(0, aliasWithSuffix.length() - BROWSER_SUFFIX.length()); - return aliasWithSuffix; - } - - public static String getAliasSuffix(String aliasWithSuffix) { - if (aliasWithSuffix.endsWith(USER_SUFFIX)) - return USER_SUFFIX; - if (aliasWithSuffix.endsWith(SYSTEM_SUFFIX)) - return SYSTEM_SUFFIX; - if (aliasWithSuffix.endsWith(BROWSER_SUFFIX)) - return BROWSER_SUFFIX; - return null; - } - @Override public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { - String alias = null; - if (userKeyManager != null) - alias = userKeyManager.chooseServerAlias(keyType, issuers, socket); - if (alias == null && systemKeyManager != null) - alias = systemKeyManager.chooseServerAlias(keyType, issuers, socket); - if (alias == null && browserKeyManager != null) - alias = browserKeyManager.chooseServerAlias(keyType, issuers, socket); - return alias; - } + @Override + public PrivateKey getPrivateKey(String alias) { + PrivateKey privateKey = null; + if (userKeyManager != null) { + privateKey = userKeyManager.getPrivateKey(alias); + } + if (privateKey == null && systemKeyManager != null) { + privateKey = systemKeyManager.getPrivateKey(alias); + } + if (privateKey == null && browserKeyManager != null) { + privateKey = browserKeyManager.getPrivateKey(alias); + } + return privateKey; + } - @Override public X509Certificate[] getCertificateChain(String alias) { - X509Certificate[] x509certificates = null; - if (userKeyManager != null) - x509certificates = userKeyManager.getCertificateChain(alias); - if (x509certificates == null && systemKeyManager != null) - x509certificates = systemKeyManager.getCertificateChain(alias); - if (x509certificates == null && browserKeyManager != null) - x509certificates = browserKeyManager.getCertificateChain(alias); - return x509certificates; - } + @Override + public String[] getServerAliases(String keyType, Principal[] issuers) { + final List aliases = new ArrayList<>(); + if (userKeyManager != null) { + addNonNullStrings(aliases, userKeyManager.getServerAliases(keyType, issuers), null); + } + if (systemKeyManager != null) { + addNonNullStrings(aliases, systemKeyManager.getServerAliases(keyType, issuers), null); + } + if (browserKeyManager != null) { + addNonNullStrings(aliases, browserKeyManager.getServerAliases(keyType, issuers), null); + } + return aliases.toArray(new String[0]); + } - @Override public String[] getClientAliases(String keyType, Principal[] issuers) { - List aliases = new ArrayList(); - if (userKeyManager != null) - addNonNullStrings(aliases, userKeyManager.getClientAliases(keyType, issuers), USER_SUFFIX); - if (systemKeyManager != null) - addNonNullStrings(aliases, systemKeyManager.getClientAliases(keyType, issuers), SYSTEM_SUFFIX); - if (browserKeyManager != null) - addNonNullStrings(aliases, browserKeyManager.getClientAliases(keyType, issuers), BROWSER_SUFFIX); - return aliases.toArray(new String[0]); - } + @Override + public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) { + return chooseClientAlias(keyType, issuers, null); + } - @Override public PrivateKey getPrivateKey(String alias) { - PrivateKey privateKey = null; - if (userKeyManager != null) - privateKey = userKeyManager.getPrivateKey(alias); - if (privateKey == null && systemKeyManager != null) - privateKey = systemKeyManager.getPrivateKey(alias); - if (privateKey == null && browserKeyManager != null) - privateKey = browserKeyManager.getPrivateKey(alias); - return privateKey; - } + @Override + public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) { + return chooseServerAlias(keyType, issuers, null); + } - @Override public String[] getServerAliases(String keyType, Principal[] issuers) { - List aliases = new ArrayList(); - if (userKeyManager != null) - addNonNullStrings(aliases, userKeyManager.getServerAliases(keyType, issuers), null); - if (systemKeyManager != null) - addNonNullStrings(aliases, systemKeyManager.getServerAliases(keyType, issuers), null); - if (browserKeyManager != null) - addNonNullStrings(aliases, browserKeyManager.getServerAliases(keyType, issuers), null); - return aliases.toArray(new String[0]); - } - - @Override public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) { - return chooseClientAlias(keyType, issuers, null); - } - - @Override public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) { - return chooseServerAlias(keyType, issuers, null); - } - - private void addNonNullStrings(List list, String[] array, String suffix) { - if (array != null) - for (String s : array) - if (s != null) - if (suffix != null) - list.add(s + suffix); - else - list.add(s); - } + private void addNonNullStrings(List list, String[] array, String suffix) { + if (array != null) { + for (String s : array) { + if (s != null) { + if (suffix != null) { + list.add(s + suffix); + } else { + list.add(s); + } + } + } + } + } } From 4114a38ca50b0c321f77f3797cd1d69908b462a0 Mon Sep 17 00:00:00 2001 From: Stephan Classen Date: Wed, 21 Apr 2021 15:00:47 +0200 Subject: [PATCH 8/9] code cleanup --- .../jnlp/runtime/MergedKeyManager.java | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/MergedKeyManager.java b/core/src/main/java/net/sourceforge/jnlp/runtime/MergedKeyManager.java index 6ddc163f4..cb4c13b09 100644 --- a/core/src/main/java/net/sourceforge/jnlp/runtime/MergedKeyManager.java +++ b/core/src/main/java/net/sourceforge/jnlp/runtime/MergedKeyManager.java @@ -3,6 +3,7 @@ import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialogs; import net.adoptopenjdk.icedteaweb.logging.Logger; import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; +import net.adoptopenjdk.icedteaweb.os.OsUtil; import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.DialogResult; import net.sourceforge.jnlp.security.SecurityUtil; @@ -26,7 +27,11 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.stream.Collectors.toList; import static net.sourceforge.jnlp.security.KeyStores.Level.SYSTEM; import static net.sourceforge.jnlp.security.KeyStores.Level.USER; import static net.sourceforge.jnlp.security.KeyStores.Type.CLIENT_CERTS; @@ -50,7 +55,7 @@ public class MergedKeyManager extends X509ExtendedKeyManager { } private X509KeyManager getX509KeyManager() { - if (System.getProperty("os.name").startsWith("Windows")) { + if (OsUtil.isWindows()) { try { final KeyStore ks = KeyStore.getInstance("Windows-MY"); ks.load(null, null); @@ -249,13 +254,13 @@ public X509Certificate[] getCertificateChain(String alias) { public String[] getClientAliases(String keyType, Principal[] issuers) { final List aliases = new ArrayList<>(); if (userKeyManager != null) { - addNonNullStrings(aliases, userKeyManager.getClientAliases(keyType, issuers), USER_SUFFIX); + aliases.addAll(appendSuffixToAll(userKeyManager.getClientAliases(keyType, issuers), USER_SUFFIX)); } if (systemKeyManager != null) { - addNonNullStrings(aliases, systemKeyManager.getClientAliases(keyType, issuers), SYSTEM_SUFFIX); + aliases.addAll(appendSuffixToAll(systemKeyManager.getClientAliases(keyType, issuers), SYSTEM_SUFFIX)); } if (browserKeyManager != null) { - addNonNullStrings(aliases, browserKeyManager.getClientAliases(keyType, issuers), BROWSER_SUFFIX); + aliases.addAll(appendSuffixToAll(browserKeyManager.getClientAliases(keyType, issuers), BROWSER_SUFFIX)); } return aliases.toArray(new String[0]); } @@ -279,13 +284,13 @@ public PrivateKey getPrivateKey(String alias) { public String[] getServerAliases(String keyType, Principal[] issuers) { final List aliases = new ArrayList<>(); if (userKeyManager != null) { - addNonNullStrings(aliases, userKeyManager.getServerAliases(keyType, issuers), null); + aliases.addAll(appendSuffixToAll(userKeyManager.getServerAliases(keyType, issuers), null)); } if (systemKeyManager != null) { - addNonNullStrings(aliases, systemKeyManager.getServerAliases(keyType, issuers), null); + aliases.addAll(appendSuffixToAll(systemKeyManager.getServerAliases(keyType, issuers), null)); } if (browserKeyManager != null) { - addNonNullStrings(aliases, browserKeyManager.getServerAliases(keyType, issuers), null); + aliases.addAll(appendSuffixToAll(browserKeyManager.getServerAliases(keyType, issuers), null)); } return aliases.toArray(new String[0]); } @@ -300,17 +305,15 @@ public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEn return chooseServerAlias(keyType, issuers, null); } - private void addNonNullStrings(List list, String[] array, String suffix) { - if (array != null) { - for (String s : array) { - if (s != null) { - if (suffix != null) { - list.add(s + suffix); - } else { - list.add(s); - } - } - } + private List appendSuffixToAll(String[] array, String suffix) { + if (array == null) { + return emptyList(); + } + if (suffix == null) { + return asList(array); } + return Stream.of(array) + .map(s -> s + suffix) + .collect(toList()); } } From ceb570ae9933007d124ce89c85fdd624d7573a6b Mon Sep 17 00:00:00 2001 From: Stephan Classen Date: Wed, 21 Apr 2021 16:35:42 +0200 Subject: [PATCH 9/9] use a list instead of a linked hash map --- .../security/ClientCertSelectionPane.java | 38 ++++++++++++------- .../dialogs/security/SecurityDialogs.java | 10 ++--- .../jnlp/runtime/MergedKeyManager.java | 32 ++++++++-------- 3 files changed, 44 insertions(+), 36 deletions(-) diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ClientCertSelectionPane.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ClientCertSelectionPane.java index ebc1a9429..875b42e58 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ClientCertSelectionPane.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/ClientCertSelectionPane.java @@ -81,13 +81,13 @@ public ClientCertSelectionPane(SecurityDialog parent, Object[] extras) { setLayout(new GridBagLayout()); final JLabel jlInfo = new JLabel("" + R("CVCertificateViewer") + ""); - final Map aliasesMap = getAliases(extras); + final List certificateOptions = getAliases(extras); final List displayedNames = new ArrayList<>(); - for (final Entry entry : aliasesMap.entrySet()) { - final String subject = SecurityUtil.getCN(entry.getValue().getSubjectX500Principal().getName()); - final String issuer = SecurityUtil.getCN(entry.getValue().getIssuerX500Principal().getName()); - final int pos = entry.getKey().lastIndexOf(" (from "); - final String source = (pos != -1) ? entry.getKey().substring(pos) : ""; + for (final CertificateOption entry : certificateOptions) { + final String subject = SecurityUtil.getCN(entry.certificate.getSubjectX500Principal().getName()); + final String issuer = SecurityUtil.getCN(entry.certificate.getIssuerX500Principal().getName()); + final int pos = entry.alias.lastIndexOf(" (from "); + final String source = (pos != -1) ? entry.alias.substring(pos) : ""; displayedNames.add(subject + ":" + issuer + source); } @@ -178,7 +178,7 @@ public void keyReleased(KeyEvent ke) { jbDetails.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - SecurityDialog.showSingleCertInfoDialog(aliasesMap.values().toArray(new X509Certificate[0])[jList.getSelectedIndex()], + SecurityDialog.showSingleCertInfoDialog(certificateOptions.get(jList.getSelectedIndex()).certificate, ClientCertSelectionPane.this); } }); @@ -193,12 +193,12 @@ public void actionPerformed(ActionEvent e) { } @SuppressWarnings("unchecked") - private Map getAliases(final Object[] extras) { + private List getAliases(final Object[] extras) { final Object firstExtra = extras[0]; - if (firstExtra instanceof Map) { - return (Map) firstExtra; + if (firstExtra instanceof List) { + return (List) firstExtra; } - return new LinkedHashMap<>(); + return new ArrayList<>(); } @Override @@ -245,20 +245,30 @@ public static void main(String[] args) throws Exception { final KeyStore ks = KeyStore.getInstance("Windows-MY"); ks.load(null, null); final Enumeration aliases = ks.aliases(); - final Map aliasesMap = new LinkedHashMap<>(); + final List certificateOptions = new ArrayList<>(); while (aliases.hasMoreElements()) { final String alias = aliases.nextElement(); final Certificate c = ks.getCertificate(alias); if (c instanceof X509Certificate) { - aliasesMap.put(alias + " (from browser keystore)", (X509Certificate) c); + certificateOptions.add(new CertificateOption(alias + " (from browser keystore)", (X509Certificate) c)); } } final JFrame f = new JFrame(); f.setMinimumSize(new Dimension(500, 300)); f.setSize(700, 300); - f.add(new ClientCertSelectionPane(null, new Object[]{aliasesMap}), BorderLayout.CENTER); + f.add(new ClientCertSelectionPane(null, new Object[]{certificateOptions}), BorderLayout.CENTER); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.pack(); f.setVisible(true); } + + public static class CertificateOption { + public final String alias; + public final X509Certificate certificate; + + public CertificateOption(final String alias, final X509Certificate certificate) { + this.alias = alias; + this.certificate = certificate; + } + } } diff --git a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogs.java b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogs.java index c7b7e1990..c56965b45 100644 --- a/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogs.java +++ b/core/src/main/java/net/adoptopenjdk/icedteaweb/client/parts/dialogs/security/SecurityDialogs.java @@ -44,14 +44,13 @@ import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.YesNoSandbox; import net.adoptopenjdk.icedteaweb.ui.swing.dialogresults.YesNoSandboxLimited; import net.sourceforge.jnlp.JNLPFile; -import net.sourceforge.jnlp.runtime.classloader.SecurityDelegate; import net.sourceforge.jnlp.runtime.JNLPRuntime; +import net.sourceforge.jnlp.runtime.classloader.SecurityDelegate; import net.sourceforge.jnlp.security.AccessType; import net.sourceforge.jnlp.security.CertVerifier; import net.sourceforge.jnlp.util.UrlUtils; import javax.swing.JDialog; - import java.awt.Dialog.ModalityType; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; @@ -59,8 +58,7 @@ import java.net.URL; import java.security.AccessController; import java.security.PrivilegedAction; -import java.security.cert.X509Certificate; -import java.util.Map; +import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.Semaphore; @@ -230,11 +228,11 @@ public static NamePassword showAuthenticationPrompt(String host, int port, Strin return (NamePassword) response; } - public static DialogResult showClientCertSelectionPrompt(Map aliases) { + public static DialogResult showClientCertSelectionPrompt(List certificateOptions) { final SecurityDialogMessage message = new SecurityDialogMessage(null); message.dialogType = DialogType.CLIENT_CERT_SELECTION; - message.extras = new Object[] {aliases}; + message.extras = new Object[] {certificateOptions}; message.showInTaskBar = true; final DialogResult response = getUserResponse(message); diff --git a/core/src/main/java/net/sourceforge/jnlp/runtime/MergedKeyManager.java b/core/src/main/java/net/sourceforge/jnlp/runtime/MergedKeyManager.java index cb4c13b09..a040d9446 100644 --- a/core/src/main/java/net/sourceforge/jnlp/runtime/MergedKeyManager.java +++ b/core/src/main/java/net/sourceforge/jnlp/runtime/MergedKeyManager.java @@ -1,5 +1,6 @@ package net.sourceforge.jnlp.runtime; +import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.ClientCertSelectionPane.CertificateOption; import net.adoptopenjdk.icedteaweb.client.parts.dialogs.security.SecurityDialogs; import net.adoptopenjdk.icedteaweb.logging.Logger; import net.adoptopenjdk.icedteaweb.logging.LoggerFactory; @@ -23,7 +24,6 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -102,9 +102,9 @@ public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket s } } - final Map validClientAliases = new LinkedHashMap<>(); - final Map expiredClientAliases = new LinkedHashMap<>(); - final Map otherAliases = new LinkedHashMap<>(); + final List validClientAliases = new ArrayList<>(); + final List expiredClientAliases = new ArrayList<>(); + final List otherAliases = new ArrayList<>(); for (final String keyType : keyTypes) { final String[] aliasesWithSuffix = getClientAliases(keyType, issuers); //if (aliasesWithSuffix != null) @@ -121,18 +121,18 @@ public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket s try { cert.checkValidity(); LOG.info("Found valid client alias with clientCert or ANY extension: " + aliasWithSuffix); - validClientAliases.put(aliasWithSuffix, cert); + validClientAliases.add(new CertificateOption(aliasWithSuffix, cert)); } catch (CertificateException e) { LOG.warn("Found expired client alias with clientCert or ANY extension: " + aliasWithSuffix); - expiredClientAliases.put(aliasWithSuffix, cert); + expiredClientAliases.add(new CertificateOption(aliasWithSuffix, cert)); } } else { LOG.warn("Found non-client alias: " + aliasWithSuffix); - otherAliases.put(aliasWithSuffix, cert); + otherAliases.add(new CertificateOption(aliasWithSuffix, cert)); } } catch (CertificateParsingException e) { LOG.warn("Exception while getting ExtendedKeyUsage for alias " + aliasWithSuffix); - otherAliases.put(aliasWithSuffix, cert); + otherAliases.add(new CertificateOption(aliasWithSuffix, cert)); } } } @@ -141,7 +141,7 @@ public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket s if (!validClientAliases.isEmpty()) { alias = getPreferredAlias(validClientAliases, "valid client"); } else { - expiredClientAliases.putAll(otherAliases); + expiredClientAliases.addAll(otherAliases); if (!expiredClientAliases.isEmpty()) { alias = getPreferredAlias(expiredClientAliases, "remaining"); } else { @@ -178,25 +178,25 @@ private String getHostFromSocket(Socket socket) { return null; } - private String getPreferredAlias(Map aliasesMap, String aliasType) { + private String getPreferredAlias(List certificateOptions, String aliasType) { final String alias; - if (aliasesMap.size() > 1) { + if (certificateOptions.size() > 1) { if (JNLPRuntime.isHeadless()) { - alias = aliasesMap.keySet().iterator().next(); + alias = certificateOptions.get(0).alias; LOG.info("Returning the first " + aliasType + " alias in headless mode : " + alias); } else { - final DialogResult res = SecurityDialogs.showClientCertSelectionPrompt(aliasesMap); + final DialogResult res = SecurityDialogs.showClientCertSelectionPrompt(certificateOptions); if (res == null) { alias = null; userCancelled = true; LOG.warn("Client certificate selection cancelled by user"); } else { - alias = aliasesMap.keySet().toArray(new String[0])[res.getButtonIndex()]; + alias = certificateOptions.get(res.getButtonIndex()).alias; LOG.info("Returning the selected " + aliasType + " alias : " + alias); } } - } else if (aliasesMap.size() == 1) { - alias = aliasesMap.keySet().iterator().next(); + } else if (certificateOptions.size() == 1) { + alias = certificateOptions.get(0).alias; LOG.info("Returning the only " + aliasType + " alias : " + alias); } else { alias = null;