Re: [rfc][icedtea-web] Certificate viewer cleanup

Jacob Wisor gitne at excite.co.jp
Wed Aug 28 06:18:24 PDT 2013


"Jacob Wisor"<gitne at xxxxxxxxxxxx> wrote:
> * Replaced old insufficient certificate viewer's action buttons handling with proper handling
> * Made System-level certificates managable if current user has write access to a KeyStore (usually only root or admin)
> * Added informative tool tip to disabled buttons when certificates are unmanagable
> * Made certificate table uneditable
> * Made certificate info table uneditable
> * Made certificate info's descriptive JTextField have the same font size as its info table for better readability
> * Added certificate file filters with Windows/Linux preference
> * Added auto-suggested certificate file names when exporting
> * Added automatically generated mnemonics
> * Made labels link to corresponding JComponents
> * Added a border to certificate type label for better readability
> * Uniformed border sizes
> * Added removing of multiple certificates at once
> * Turned some only once occuring fields into static
> * Added proper exception handling when setting system look & feel, hence removed superfluous "throws Exception" from method signatures
> 
> Happy reviewing!
> 
> Jacob

Pasting in this time.

diff -r 94ebabfba6ab netx/net/sourceforge/jnlp/resources/Messages.properties
--- a/netx/net/sourceforge/jnlp/resources/Messages.properties
+++ b/netx/net/sourceforge/jnlp/resources/Messages.properties
@@ -275,6 +276,9 @@
 SValidity=Validity
 
 # Certificate Viewer
+CVCannotImport=You can not import certificates of this type because {0}
+CVCannotRemove=You can not remove certificates of this type because {0}
+CVCause=<ul><li>you are not logged in as an administrator,</li><li>you have no file system write permission to the keystore file,</li><li>the keystore file is inaccesible via network,</il><li>the keystore file is write protected by file system attributes,</li><li>you do not have a <code>FilePermission("write")</code>, or</li><li>the keystore file is otherwise physically inaccesible.</li></ul>
 CVCertificateViewer=Certificates
 CVCertificateType=Certificate Type
 CVDetails=Details
diff -r 94ebabfba6ab netx/net/sourceforge/jnlp/resources/Messages_de.properties
--- a/netx/net/sourceforge/jnlp/resources/Messages_de.properties
+++ b/netx/net/sourceforge/jnlp/resources/Messages_de.properties
@@ -269,6 +271,9 @@
 SValidity=Gu00fcltigkeit
 
 # Certificate Viewer
+CVCannotImport=Es ku00f6nnen keine Zertifikate dieses Typs importiert werden, da {0}
+CVCannotRemove=Es ku00f6nnen keine Zertifikate dieses Typs entfernt werden, da {0}
+CVCause=<ul><li>das aktuell verwendete Benutzerkonto kein Administratorkonto ist,</li><li>das aktuell verwendete Benutzerkonto keine Schreibberechtigung fu00fcr die Schlu00fcsselspeicherdatei hat,</li><li>die Schlu00fcsselspeicherdatei ist u00fcber das Netzwerk unerreichbar,</il><li>die Schlu00fcsselspeicherdatei ist durch Dateisystemattribute schreibgeschu00fctzt,</li><li>das aktuell verwendete Benutzerkonto keine <code>FilePermission("write")</code> besitzt oder</li><li>die Schlu00fcsselspeicherdatei auf sonstige physikalische Weise nicht verfu00fcgbar ist.</li></ul>
 CVCertificateViewer=Zertifikate
 CVCertificateType=Zertifikattyp
 CVDetails=Details
diff -r 94ebabfba6ab netx/net/sourceforge/jnlp/resources/Messages_pl.properties
--- a/netx/net/sourceforge/jnlp/resources/Messages_pl.properties
+++ b/netx/net/sourceforge/jnlp/resources/Messages_pl.properties
@@ -269,6 +271,9 @@
 SValidity=Wau017cnou015bu0107
 
 # Certificate Viewer
+CVCannotImport=Nie mou017cna importowau0107 certyfikatu00f3w tego typu poniewau017c {0}
+CVCannotRemove=Nie mou017cna usunu0105u0107 certyfikatu00f3w tego typu poniewau017c {0}
+CVCause=<ul><li>nie jesteu015b zalogowany jako administrator,</li><li>nie posiadasz uprawnienia zapisu systemu plikowego do pliku bazy kluczy,</li><li>plik bazy kluczy jest niedostu0119pny w sieci,</il><li>atrybuty systemu plikowego chroniu0105 plik bazy kluczy przed zapisem,</li><li>nie posiadasz <code>FilePermission("write")</code> lub</li><li>plik bazy kluczy jest fizycznie niedostu0119pny.</li></ul>
 CVCertificateViewer=Certyfikaty
 CVCertificateType=Typ certyfikatu
 CVDetails=Szczegu00f3u0142y
diff -r 94ebabfba6ab netx/net/sourceforge/jnlp/security/CertsInfoPane.java
--- a/netx/net/sourceforge/jnlp/security/CertsInfoPane.java
+++ b/netx/net/sourceforge/jnlp/security/CertsInfoPane.java
@@ -56,6 +56,8 @@
 import javax.swing.tree.DefaultMutableTreeNode;
 import javax.swing.tree.TreeSelectionModel;
 
+import net.sourceforge.jnlp.util.UI.NonEditableTableModel;
+
 /**
  * Provides the panel for the Certificate Info dialog. This dialog displays data from
  * X509Certificate(s) used in jar signing.
@@ -72,7 +74,7 @@
     private ListSelectionModel listSelectionModel;
     private ListSelectionModel tableSelectionModel;
     protected String[] certNames;
-    private String[] columnNames = { R("Field"), R("Value") };
+    private static final String[] columnNames = { R("Field"), R("Value") };
     protected ArrayList<String[][]> certsData;
 
     public CertsInfoPane(SecurityDialog x, CertVerifier certVerifier) {
@@ -150,11 +152,11 @@
         try {
             MessageDigest digest = MessageDigest.getInstance("MD5");
             digest.update(c.getEncoded());
-            md5Hash = makeFingerprint(digest.digest());
+            md5Hash = CertsInfoPane.makeFingerprint(digest.digest());
 
             digest = MessageDigest.getInstance("SHA-1");
             digest.update(c.getEncoded());
-            sha1Hash = makeFingerprint(digest.digest());
+            sha1Hash = CertsInfoPane.makeFingerprint(digest.digest());
         } catch (Exception e) {
             //fail quietly
         }
@@ -189,8 +191,8 @@
         JScrollPane listPane = new JScrollPane(tree);
 
         //Table of field-value pairs
-        DefaultTableModel tableModel = new DefaultTableModel(certsData.get(0),
-                                                            columnNames);
+        NonEditableTableModel tableModel = new NonEditableTableModel(this.certsData.get(0),
+                CertsInfoPane.columnNames);
         table = new JTable(tableModel);
         table.getTableHeader().setReorderingAllowed(false);
         tableSelectionModel = table.getSelectionModel();
@@ -202,6 +204,7 @@
         //Text area to display the larger values
         output = new JTextArea();
         output.setEditable(false);
+        output.setFont(new Font(Font.MONOSPACED, Font.PLAIN, this.table.getFont().getSize()));
         JScrollPane outputPane = new JScrollPane(output,
                 ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
@@ -275,11 +278,9 @@
             if (node == null)
                 return;
             if (node.isRoot()) {
-                table.setModel(new DefaultTableModel(certsData.get(0),
-                                        columnNames));
+                table.setModel(new NonEditableTableModel(certsData.get(0), CertsInfoPane.columnNames));
             } else if (node.isLeaf()) {
-                table.setModel(new DefaultTableModel(certsData.get(1),
-                                        columnNames));
+                table.setModel(new NonEditableTableModel(certsData.get(1), CertsInfoPane.columnNames));
             }
         }
     }
@@ -296,8 +297,8 @@
 
             for (int i = minIndex; i <= maxIndex; i++) {
                 if (lsm.isSelectedIndex(i)) {
-                    table.setModel(new DefaultTableModel(certsData.get(i),
-                                                            columnNames));
+                    table.setModel(new NonEditableTableModel(certsData.get(i),
+                            CertsInfoPane.columnNames));
                 }
             }
         }
@@ -326,7 +327,7 @@
      * Makes a human readable hash fingerprint.
      * For example: 11:22:33:44:AA:BB:CC:DD:EE:FF.
      */
-    private String makeFingerprint(byte[] hash) {
+    private static final String makeFingerprint(byte[] hash) {
         String fingerprint = "";
         for (int i = 0; i < hash.length; i++) {
             if (!fingerprint.equals(""))
diff -r 94ebabfba6ab netx/net/sourceforge/jnlp/security/KeyStores.java
--- a/netx/net/sourceforge/jnlp/security/KeyStores.java
+++ b/netx/net/sourceforge/jnlp/security/KeyStores.java
@@ -39,6 +39,7 @@
 
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.security.AllPermission;
@@ -64,12 +65,12 @@
 
     /* this gets turned into user-readable strings, see toUserReadableString */
 
-    public enum Level {
+    public static enum Level {
         USER,
         SYSTEM,
     }
 
-    public enum Type {
+    public static enum Type {
         CERTS,
         JSSE_CERTS,
         CA_CERTS,
@@ -302,6 +303,37 @@
     }
 
     /**
+     * Checks whether the specified system-level or user-level {@link KeyStore}
+     * is writable.
+     * @param level 
+     * @return {@code true} if the specified {@code KeyStore} is writable,
+     * {@code false} otherwise
+     * @see KeyStores.Level
+     */
+    public static final boolean isKeyStoreWriteable(KeyStores.Level level, KeyStores.Type type) {
+        if (level == null) throw new IllegalArgumentException("level == " + level);
+        if (type == null) throw new IllegalArgumentException("type == " + type);
+
+        FileOutputStream fileOutputStream = null;
+
+        try {
+            fileOutputStream = new FileOutputStream(KeyStores.getKeyStoreLocation(level, type), true);
+        } catch (final FileNotFoundException fileNotFoundException) {
+            fileOutputStream = null;
+        } catch (final SecurityException securityException) {
+            fileOutputStream = null;
+        } finally {
+            if (fileOutputStream != null) try {
+                fileOutputStream.close();
+            } catch (final IOException ioException) {
+                // Don't bother, fileOutputStream is probably closed anyway
+            }
+        }
+
+        return fileOutputStream != null;
+    }
+
+    /**
      * Returns a String that can be used as a translation key to create a
      * user-visible representation of this KeyStore. Creates a string by
      * concatenating a level and type, converting everything to Title Case and
diff -r 94ebabfba6ab netx/net/sourceforge/jnlp/security/viewer/CertificatePane.java
--- a/netx/net/sourceforge/jnlp/security/viewer/CertificatePane.java
+++ b/netx/net/sourceforge/jnlp/security/viewer/CertificatePane.java
@@ -45,7 +45,6 @@
 import java.awt.GridLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
-import java.awt.event.KeyEvent;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.OutputStream;
@@ -56,6 +55,7 @@
 import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.List;
+import java.util.Map;
 
 import javax.swing.BorderFactory;
 import javax.swing.JButton;
@@ -70,16 +70,22 @@
 import javax.swing.JScrollPane;
 import javax.swing.JTabbedPane;
 import javax.swing.JTable;
+import javax.swing.ListSelectionModel;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
-import javax.swing.table.DefaultTableModel;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.filechooser.FileNameExtensionFilter;
 
+import net.sourceforge.jnlp.runtime.JNLPRuntime;
 import net.sourceforge.jnlp.security.CertificateUtils;
 import net.sourceforge.jnlp.security.KeyStores;
 import net.sourceforge.jnlp.security.SecurityUtil;
 import net.sourceforge.jnlp.security.SecurityDialog;
 import net.sourceforge.jnlp.security.KeyStores.Level;
 import net.sourceforge.jnlp.util.FileUtils;
+import net.sourceforge.jnlp.util.UI;
+import net.sourceforge.jnlp.util.UI.NonEditableTableModel;
 
 public class CertificatePane extends JPanel {
 
@@ -94,9 +100,14 @@
      * "Issued To" and "Issued By" string pairs for certs.
      */
     private String[][] issuedToAndBy = null;
-    private final String[] columnNames = { R("CVIssuedTo"), R("CVIssuedBy") };
+    private final static String[] columnNames = { R("CVIssuedTo"), R("CVIssuedBy") };
+    private final static String CER_EXTENSION = "cer",
+                                CRT_EXTENSION = "crt",
+                                PFX_EXTENSION = "pfx",
+                                P12_EXTENSION = "p12",
+                                PKCS12 = "PKCS #12";
 
-    private final CertificateType[] certificateTypes = new CertificateType[] {
+    private final static CertificateType[] certificateTypes = new CertificateType[] {
             new CertificateType(KeyStores.Type.CA_CERTS),
             new CertificateType(KeyStores.Type.JSSE_CA_CERTS),
             new CertificateType(KeyStores.Type.CERTS),
@@ -108,12 +119,13 @@
     private final JTable userTable;
     private final JTable systemTable;
     private JComboBox certificateTypeCombo;
+    private final JButton importButton,
+                          exportButton,
+                          removeButton,
+                          detailButton;
     private KeyStores.Type currentKeyStoreType;
     private KeyStores.Level currentKeyStoreLevel;
 
-    /** JComponents that should be disbled for system store */
-    private final List<JComponent> disableForSystem;
-
     private JDialog parent;
     private JComponent defaultFocusComponent = null;
 
@@ -122,6 +134,19 @@
      * the user. This KeyStore corresponds to that.
      */
     private KeyStore keyStore = null;
+    /**
+     * Indicates whether the currently selected {@link KeyStore} is writeable. The
+     * current selection is based on the combination of {@link CertificatePane#tabbedPane}
+     * and {@link CertificatePane#certificateTypeCombo}.<br/>
+     * Sub-classes of {@code CertificatePane} altering the behavior of those
+     * {@code JComponent}s cannot rely on the accuracy or validity of this field.
+     * @see KeyStores#isKeyStoreWriteable(KeyStores.Level,KeyStores.Type)
+     * @see CertificatePane#tabbedPane
+     * @see CertificatePane#certificateTypeCombo
+     * @see CertificatePane.TabChangeListener#stateChanged(ChangeEvent)
+     * @see CertificatePane.CertificateTypeListener#actionPerformed(ActionEvent)
+     */
+    protected boolean isKeyStoreWriteable;
 
     public CertificatePane(JDialog parent) {
         super();
@@ -129,7 +154,10 @@
 
         userTable = new JTable(null);
         systemTable = new JTable(null);
-        disableForSystem = new ArrayList<JComponent>();
+        this.importButton = new JButton();
+        this.exportButton = new JButton();
+        this.removeButton = new JButton();
+        this.detailButton = new JButton();
 
         addComponents();
 
@@ -139,6 +167,9 @@
         } else {
             currentKeyStoreLevel = Level.SYSTEM;
         }
+        this.importButton.setEnabled(this.isKeyStoreWriteable =
+                KeyStores.isKeyStoreWriteable(this.currentKeyStoreLevel,
+                        this.currentKeyStoreType));
 
         repopulateTables();
     }
@@ -163,9 +194,11 @@
         certificateTypePanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
 
         JLabel certificateTypeLabel = new JLabel(R("CVCertificateType"));
+        certificateTypeLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5));
 
         certificateTypeCombo = new JComboBox(certificateTypes);
         certificateTypeCombo.addActionListener(new CertificateTypeListener());
+        certificateTypeLabel.setLabelFor(this.certificateTypeCombo);
 
         certificateTypePanel.add(certificateTypeLabel, BorderLayout.LINE_START);
         certificateTypePanel.add(certificateTypeCombo, BorderLayout.CENTER);
@@ -173,20 +206,24 @@
         JPanel tablePanel = new JPanel(new BorderLayout());
 
         // User Table
-        DefaultTableModel userTableModel = new DefaultTableModel(issuedToAndBy, columnNames);
-        userTable.setModel(userTableModel);
+        userTable.setModel(new NonEditableTableModel(issuedToAndBy, CertificatePane.columnNames));
         userTable.getTableHeader().setReorderingAllowed(false);
         userTable.setFillsViewportHeight(true);
+        final CertificatePane.CertificateListSelectionListener certificateListSelectionListener;
+        userTable.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+        userTable.getSelectionModel().addListSelectionListener(certificateListSelectionListener =
+                new CertificatePane.CertificateListSelectionListener());
         JScrollPane userTablePane = new JScrollPane(userTable);
         userTablePane.setPreferredSize(TABLE_DIMENSION);
         userTablePane.setSize(TABLE_DIMENSION);
         userTablePane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
 
         // System Table
-        DefaultTableModel systemTableModel = new DefaultTableModel(issuedToAndBy, columnNames);
-        systemTable.setModel(systemTableModel);
+        systemTable.setModel(new NonEditableTableModel(issuedToAndBy, CertificatePane.columnNames));
         systemTable.getTableHeader().setReorderingAllowed(false);
         systemTable.setFillsViewportHeight(true);
+        systemTable.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+        systemTable.getSelectionModel().addListSelectionListener(certificateListSelectionListener);
         JScrollPane systemTablePane = new JScrollPane(systemTable);
         systemTablePane.setPreferredSize(TABLE_DIMENSION);
         systemTablePane.setSize(TABLE_DIMENSION);
@@ -199,35 +236,50 @@
 
         JPanel buttonPanel = new JPanel(new FlowLayout());
 
-        String[] buttonNames = { R("CVImport"), R("CVExport"), R("CVRemove"), R("CVDetails") };
-        char[] buttonMnemonics = { KeyEvent.VK_I,
-                                                                        KeyEvent.VK_E,
-                                                                        KeyEvent.VK_M,
-                                                                        KeyEvent.VK_D };
-        ActionListener[] listeners = { new ImportButtonListener(),
-                                                                                new ExportButtonListener(),
-                                                                                new RemoveButtonListener(),
-                                                                                new DetailsButtonListener() };
-        JButton button;
+        final String[] controlTexts = {
+            R("CVCertificateType"),
+            R("CVImport"),
+            R("CVExport"),
+            R("CVRemove"),
+            R("CVDetails"),
+            R("ButClose")
+        };
+        final Map<String, Character> mnemonicsMap = UI.generateMnemonics(controlTexts);
+        certificateTypeLabel.setDisplayedMnemonic(mnemonicsMap.get(R("CVCertificateType")));
 
         //get the max width
         int maxWidth = 0;
-        for (int i = 0; i < buttonNames.length; i++) {
-            button = new JButton(buttonNames[i]);
+        for (int i = 1; i < 5; i++) {
+            JButton button = new JButton(controlTexts[i]);
             maxWidth = Math.max(maxWidth, button.getMinimumSize().width);
         }
 
-        for (int i = 0; i < buttonNames.length; i++) {
-            button = new JButton(buttonNames[i]);
-            button.setMnemonic(buttonMnemonics[i]);
-            button.addActionListener(listeners[i]);
-            button.setSize(maxWidth, button.getSize().height);
-            // import and remove buttons
-            if (i == 0 || i == 2) {
-                disableForSystem.add(button);
-            }
-            buttonPanel.add(button);
-        }
+        this.importButton.setText(controlTexts[1]);
+        this.importButton.setMnemonic(mnemonicsMap.get(controlTexts[1]));
+        this.importButton.addActionListener(new CertificatePane.ImportButtonListener());
+        this.importButton.setSize(maxWidth, this.importButton.getSize().height);
+        buttonPanel.add(this.importButton);
+
+        this.exportButton.setText(controlTexts[2]);
+        this.exportButton.setMnemonic(mnemonicsMap.get(controlTexts[2]));
+        this.exportButton.addActionListener(new CertificatePane.ExportButtonListener());
+        this.exportButton.setSize(maxWidth, this.exportButton.getSize().height);
+        this.exportButton.setEnabled(false);
+        buttonPanel.add(this.exportButton);
+
+        this.removeButton.setText(controlTexts[3]);
+        this.removeButton.setMnemonic(mnemonicsMap.get(controlTexts[3]));
+        this.removeButton.addActionListener(new CertificatePane.RemoveButtonListener());
+        this.removeButton.setSize(maxWidth, this.removeButton.getSize().height);
+        this.removeButton.setEnabled(false);
+        buttonPanel.add(this.removeButton);
+
+        this.detailButton.setText(controlTexts[4]);
+        this.detailButton.setMnemonic(mnemonicsMap.get(controlTexts[4]));
+        this.detailButton.addActionListener(new CertificatePane.DetailsButtonListener());
+        this.detailButton.setSize(maxWidth, this.detailButton.getSize().height);
+        this.detailButton.setEnabled(false);
+        buttonPanel.add(this.detailButton);
 
         tablePanel.add(tabbedPane, BorderLayout.CENTER);
         tablePanel.add(buttonPanel, BorderLayout.SOUTH);
@@ -237,17 +289,18 @@
 
         if (parent != null) {
             JPanel closePanel = new JPanel(new BorderLayout());
-            closePanel.setBorder(BorderFactory.createEmptyBorder(7, 7, 7, 7));
-            JButton closeButton = new JButton(R("ButClose"));
+            closePanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+            final String buttonText;
+            JButton closeButton = new JButton(buttonText = R("ButClose"));
             closeButton.addActionListener(new CloseButtonListener());
+            closeButton.setMnemonic(mnemonicsMap.get(buttonText));
             defaultFocusComponent = closeButton;
             closePanel.add(closeButton, BorderLayout.EAST);
             main.add(closePanel, BorderLayout.SOUTH);
         }
 
-        setLayout(new GridLayout(0,1));
+        setLayout(new GridLayout(0, 1));
         add(main);
-
     }
 
     /**
@@ -289,12 +342,12 @@
     private void repopulateTables() {
         initializeKeyStore();
         readKeyStore();
-        DefaultTableModel tableModel = new DefaultTableModel(issuedToAndBy, columnNames);
 
-        userTable.setModel(tableModel);
+        userTable.setModel(new NonEditableTableModel(issuedToAndBy,
+                CertificatePane.columnNames));
 
-        tableModel = new DefaultTableModel(issuedToAndBy, columnNames);
-        systemTable.setModel(tableModel);
+        systemTable.setModel(new NonEditableTableModel(issuedToAndBy,
+                CertificatePane.columnNames));
     }
 
     public void focusOnDefaultButton() {
@@ -305,8 +358,12 @@
 
     private char[] getPassword(final String label) {
         JPasswordField jpf = new JPasswordField();
+        final JLabel jl = new JLabel(label);
+        final String passwordTitle;
+        jl.setDisplayedMnemonic(UI.generateMnemonics(new String[] { label }).get(label));
+        jl.setLabelFor(jpf);
         int result = JOptionPane.showConfirmDialog(parent,
-                                new Object[]{label, jpf},  R("CVPasswordTitle"),
+                                new Object[] {jl, jpf}, R("CVPasswordTitle"),
                                 JOptionPane.OK_CANCEL_OPTION,
                                 JOptionPane.INFORMATION_MESSAGE);
         if (result == JOptionPane.OK_OPTION)
@@ -339,6 +396,15 @@
             JComboBox source = (JComboBox) e.getSource();
             CertificateType type = (CertificateType) source.getSelectedItem();
             currentKeyStoreType = type.getType();
+            importButton.setEnabled(isKeyStoreWriteable =
+                    KeyStores.isKeyStoreWriteable(currentKeyStoreLevel, currentKeyStoreType));
+            if (isKeyStoreWriteable) {
+                importButton.setToolTipText(null);
+                removeButton.setToolTipText(null);
+            } else {
+                importButton.setToolTipText("<html><body>" + R("CVCannotImport") + "</body></html>");
+                removeButton.setToolTipText("<html><body>" + R("CVCannotRemove") + "</body></html>");
+            }
             repopulateTables();
         }
     }
@@ -354,19 +420,20 @@
             switch (source.getSelectedIndex()) {
                 case 0:
                     currentKeyStoreLevel = Level.USER;
-                    for (JComponent component : disableForSystem) {
-                        component.setEnabled(true);
-                    }
                     break;
                 case 1:
                     currentKeyStoreLevel = Level.SYSTEM;
-                    for (JComponent component : disableForSystem) {
-                        component.setEnabled(false);
-                    }
-                    break;
+            }
+            importButton.setEnabled(isKeyStoreWriteable =
+                    KeyStores.isKeyStoreWriteable(currentKeyStoreLevel, currentKeyStoreType));
+            if (isKeyStoreWriteable) {
+                importButton.setToolTipText(null);
+                removeButton.setToolTipText(null);
+            } else {
+                importButton.setToolTipText("<html><body>" + R("CVCannotImport", R("CVCause")) + "</body></html>");
+                removeButton.setToolTipText("<html><body>" + R("CVCannotRemove", R("CVCause")) + "</body></html>");
             }
             repopulateTables();
-
         }
     }
 
@@ -374,6 +441,19 @@
         public void actionPerformed(ActionEvent e) {
 
             JFileChooser chooser = new JFileChooser();
+            if (currentKeyStoreType == KeyStores.Type.CLIENT_CERTS) {
+                chooser.addChoosableFileFilter(new FileNameExtensionFilter(CertificatePane.PKCS12 +
+                        " (*." + CertificatePane.PFX_EXTENSION + File.pathSeparator +
+                        "*." + CertificatePane.P12_EXTENSION + ")",
+                        CertificatePane.PFX_EXTENSION, CertificatePane.P12_EXTENSION));
+            } else {
+                chooser.addChoosableFileFilter(new FileNameExtensionFilter(R("CVCertificateViewer") +
+                        " (*." + CertificatePane.CER_EXTENSION + File.pathSeparator +
+                        "*." + CertificatePane.CRT_EXTENSION + ")",
+                        CertificatePane.CER_EXTENSION, CertificatePane.CRT_EXTENSION));
+            }
+            chooser.setFileFilter(chooser.getChoosableFileFilters()[1]);
+            chooser.setFileHidingEnabled(false);
             int returnVal = chooser.showOpenDialog(parent);
             if (returnVal == JFileChooser.APPROVE_OPTION) {
                 try {
@@ -425,24 +505,41 @@
 
             try {
                 int selectedRow = table.getSelectedRow();
-                if (selectedRow != -1) {
-                    JFileChooser chooser = new JFileChooser();
-                    int returnVal = chooser.showOpenDialog(parent);
-                    if (returnVal == JFileChooser.APPROVE_OPTION) {
-                        String alias = keyStore.getCertificateAlias(certs
-                                                        .get(selectedRow));
-                        if (alias != null) {
-                            if (currentKeyStoreType == KeyStores.Type.CLIENT_CERTS) {
-                                char[] password = getPassword(R("CVExportPasswordMessage"));
-                                if (password != null)
-                                    CertificateUtils.dumpPKCS12(alias, chooser.getSelectedFile(), keyStore, password);
+                JFileChooser chooser = new JFileChooser();
+                chooser.setFileHidingEnabled(false);
+                final String extension;
+                if (currentKeyStoreType == KeyStores.Type.CLIENT_CERTS) {
+                    extension = "." + CertificatePane.PFX_EXTENSION;
+                    chooser.addChoosableFileFilter(new FileNameExtensionFilter(CertificatePane.PKCS12 +
+                            " (*." + CertificatePane.PFX_EXTENSION + File.pathSeparator +
+                            "*." + CertificatePane.P12_EXTENSION + ")",
+                            CertificatePane.PFX_EXTENSION, CertificatePane.P12_EXTENSION));
+                } else {
+                    extension = "." + (JNLPRuntime.isWindows() ? CertificatePane.CER_EXTENSION : CertificatePane.CRT_EXTENSION);
+                    chooser.addChoosableFileFilter(new FileNameExtensionFilter(R("CVCertificateViewer") +
+                            " (*." + CertificatePane.CER_EXTENSION + File.pathSeparator +
+                            "*." + CertificatePane.CRT_EXTENSION + ")",
+                            CertificatePane.CER_EXTENSION, CertificatePane.CRT_EXTENSION));
+                }
+                chooser.setFileFilter(chooser.getChoosableFileFilters()[1]);
+                chooser.setSelectedFile(new File(
+                        (String)table.getModel().getValueAt(selectedRow, 0) +
+                        extension));
+                int returnVal = chooser.showSaveDialog(parent);
+                if (returnVal == JFileChooser.APPROVE_OPTION) {
+                    String alias = keyStore.getCertificateAlias(certs
+                                                    .get(selectedRow));
+                    if (alias != null) {
+                        if (currentKeyStoreType == KeyStores.Type.CLIENT_CERTS) {
+                            char[] password = getPassword(R("CVExportPasswordMessage"));
+                            if (password != null)
+                                CertificateUtils.dumpPKCS12(alias, chooser.getSelectedFile(), keyStore, password);
                             } else {
                                 Certificate c = keyStore.getCertificate(alias);
                                 PrintStream ps = new PrintStream(chooser.getSelectedFile().getAbsolutePath());
                                 CertificateUtils.dump(c, ps);
                             }
-                            repopulateTables();
-                        }
+                        repopulateTables();
                     }
                 }
             } catch (Exception ex) {
@@ -468,28 +565,26 @@
             try {
                 int selectedRow = table.getSelectedRow();
 
-                if (selectedRow != -1) {
-                    String alias = keyStore.getCertificateAlias(certs.get(selectedRow));
-                    if (alias != null) {
+                String alias = keyStore.getCertificateAlias(certs.get(selectedRow));
+                if (alias != null) {
 
-                        int i = JOptionPane.showConfirmDialog(parent,
-                                                        R("CVRemoveConfirmMessage"),
-                                                        R("CVRemoveConfirmTitle"),
-                                                        JOptionPane.YES_NO_OPTION);
-                        if (i == 0) {
-                            keyStore.deleteEntry(alias);
-                            File keyStoreFile = new File(KeyStores
-                                                        .getKeyStoreLocation(currentKeyStoreLevel, currentKeyStoreType));
-                            if (!keyStoreFile.isFile()) {
-                                FileUtils.createRestrictedFile(keyStoreFile, true);
-                            }
-                            FileOutputStream fos = new FileOutputStream(keyStoreFile);
-                            keyStore.store(fos, KeyStores.getPassword());
-                            fos.close();
+                    int i = JOptionPane.showConfirmDialog(parent,
+                                                    R("CVRemoveConfirmMessage"),
+                                                    R("CVRemoveConfirmTitle"),
+                                                    JOptionPane.YES_NO_OPTION);
+                    if (i == 0) {
+                        keyStore.deleteEntry(alias);
+                        File keyStoreFile = new File(KeyStores
+                                                    .getKeyStoreLocation(currentKeyStoreLevel, currentKeyStoreType));
+                        if (!keyStoreFile.isFile()) {
+                            FileUtils.createRestrictedFile(keyStoreFile, true);
                         }
+                        FileOutputStream fos = new FileOutputStream(keyStoreFile);
+                        keyStore.store(fos, KeyStores.getPassword());
+                        fos.close();
                     }
-                    repopulateTables();
                 }
+                repopulateTables();
             } catch (Exception ex) {
                 // TODO
                 ex.printStackTrace();
@@ -513,10 +608,8 @@
             }
 
             int selectedRow = table.getSelectedRow();
-            if (selectedRow != -1 && selectedRow >= 0) {
-                X509Certificate c = certs.get(selectedRow);
-                SecurityDialog.showSingleCertInfoDialog(c, parent);
-            }
+            X509Certificate c = certs.get(selectedRow);
+            SecurityDialog.showSingleCertInfoDialog(c, parent);
         }
     }
 
@@ -527,4 +620,27 @@
         }
     }
 
+    private final class CertificateListSelectionListener implements ListSelectionListener {
+        public void valueChanged(ListSelectionEvent listSelectionEvent) {
+            final ListSelectionModel listSelectionModel =
+                    (ListSelectionModel)listSelectionEvent.getSource();
+
+            if (listSelectionModel.isSelectionEmpty()) {
+                exportButton.setEnabled(false);
+                removeButton.setEnabled(false);
+                detailButton.setEnabled(false);
+            } else {
+                // Has only one row been selected?
+                if (listSelectionModel.getMinSelectionIndex() ==
+                        listSelectionModel.getMaxSelectionIndex()) {
+                    exportButton.setEnabled(true);
+                    detailButton.setEnabled(true);
+                } else {
+                    exportButton.setEnabled(false);
+                    detailButton.setEnabled(false);
+                }
+                removeButton.setEnabled(isKeyStoreWriteable);
+            }
+        }
+    }
 }
diff -r 94ebabfba6ab netx/net/sourceforge/jnlp/security/viewer/CertificateViewer.java
--- a/netx/net/sourceforge/jnlp/security/viewer/CertificateViewer.java
+++ b/netx/net/sourceforge/jnlp/security/viewer/CertificateViewer.java
@@ -49,6 +49,7 @@
 
 import javax.swing.JDialog;
 import javax.swing.UIManager;
+import javax.swing.UnsupportedLookAndFeelException;
 
 import net.sourceforge.jnlp.runtime.JNLPRuntime;
 import net.sourceforge.jnlp.util.ImageResources;
@@ -98,7 +99,7 @@
         ScreenFinder.centerWindowsToCurrentScreen(this);
     }
 
-    public static void showCertificateViewer() throws Exception {
+    public static void showCertificateViewer() {
         JNLPRuntime.initialize(true);
         setSystemLookAndFeel();
 
@@ -112,12 +113,16 @@
     private static void setSystemLookAndFeel() {
         try {
             UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
-        } catch (Exception e) {
+        } catch (ClassNotFoundException classNotFoundException) {}
+          catch (InstantiationException instantiationException) {}
+          catch (IllegalAccessException illegalAccessException) {}
+          catch (UnsupportedLookAndFeelException unsupportedLookAndFeelException) {}
+          catch (ClassCastException classCastException) {
             // don't worry if we can't.
         }
     }
 
-    public static void main(String[] args) throws Exception {
+    public static void main(String[] args) {
         CertificateViewer.showCertificateViewer();
     }
 }



More information about the distro-pkg-dev mailing list