From e51decb08db88b7c03683d6124b854cd9719867c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Kil=C3=A5s?= Date: Sat, 2 Aug 2014 13:58:27 +0200 Subject: [PATCH 1/7] Initial plumbing for improved certificate panel. --- .../VerifyCertificateDialogImpl.java | 43 ++++++++++++------- .../desktoputil/ViewCertificateFrame.java | 15 ++++--- .../desktoputil/X509CertificatePanel.java | 16 ++++++- 3 files changed, 51 insertions(+), 23 deletions(-) diff --git a/src/net/java/sip/communicator/plugin/desktoputil/VerifyCertificateDialogImpl.java b/src/net/java/sip/communicator/plugin/desktoputil/VerifyCertificateDialogImpl.java index 62415b5ef..6fba19222 100644 --- a/src/net/java/sip/communicator/plugin/desktoputil/VerifyCertificateDialogImpl.java +++ b/src/net/java/sip/communicator/plugin/desktoputil/VerifyCertificateDialogImpl.java @@ -9,8 +9,10 @@ import java.awt.*; import java.awt.event.*; import java.security.cert.*; +import java.util.*; import javax.swing.*; +import javax.swing.border.*; import net.java.sip.communicator.service.certificate.*; import net.java.sip.communicator.util.*; @@ -54,6 +56,7 @@ class VerifyCertificateDialogImpl * The certificate to show. */ Certificate cert; + java.util.List certs; /** * A text that describes why the verification failed. @@ -110,7 +113,15 @@ public VerifyCertificateDialogImpl(Certificate[] certs, R.getI18NString("service.gui.CERT_DIALOG_TITLE")); setModal(true); - // for now shows only the first certificate from the chain + this.certs = new ArrayList(); + for (Certificate certificate : certs) + { + if (certificate instanceof X509Certificate) { + this.certs.add((X509Certificate) certificate); + } + } + // for now shows only the first certificate from the chain for + // non X.509 certificates this.cert = certs[0]; this.message = message; @@ -224,13 +235,14 @@ private void actionShowCertificate() return; } - certPanel.setLayout(new BorderLayout()); + certPanel.setLayout(new BorderLayout(5, 5)); + certPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); certPanel.add(alwaysTrustCheckBox, BorderLayout.NORTH); Component certInfoPane = null; - if(cert instanceof X509Certificate) + if (!certs.isEmpty()) { - certInfoPane = new X509CertificatePanel((X509Certificate)cert); + certInfoPane = new X509CertificatePanel(certs); } else { @@ -238,20 +250,21 @@ private void actionShowCertificate() textArea.setOpaque(false); textArea.setEditable(false); textArea.setText(cert.toString()); - certInfoPane = textArea; - } - final JScrollPane certScroll = new JScrollPane(certInfoPane); - certScroll.setPreferredSize(new Dimension(300, 300)); - certPanel.add(certScroll, BorderLayout.CENTER); + final JScrollPane certScroll = new JScrollPane(certInfoPane); + certScroll.setPreferredSize(new Dimension(300, 300)); - SwingUtilities.invokeLater(new Runnable() - { - public void run() + SwingUtilities.invokeLater(new Runnable() { - certScroll.getVerticalScrollBar().setValue(0); - } - }); + public void run() + { + certScroll.getVerticalScrollBar().setValue(0); + } + }); + + certInfoPane = certScroll; + } + certPanel.add(certInfoPane, BorderLayout.CENTER); certButton.setText(R.getI18NString("service.gui.HIDE_CERT")); diff --git a/src/net/java/sip/communicator/plugin/desktoputil/ViewCertificateFrame.java b/src/net/java/sip/communicator/plugin/desktoputil/ViewCertificateFrame.java index 3c6bf336f..1afc95e06 100644 --- a/src/net/java/sip/communicator/plugin/desktoputil/ViewCertificateFrame.java +++ b/src/net/java/sip/communicator/plugin/desktoputil/ViewCertificateFrame.java @@ -8,6 +8,7 @@ import java.awt.*; import java.security.cert.*; +import java.util.Arrays; import javax.swing.*; import org.jitsi.service.resources.*; @@ -39,9 +40,9 @@ public class ViewCertificateFrame private static final int MAX_MSG_PANE_HEIGHT = 800; /** - * The certificate to show. + * The certificates to show. */ - Certificate cert; + Certificate[] certs; /** * A text that describes why the verification failed. @@ -74,8 +75,7 @@ public ViewCertificateFrame(Certificate[] certs, setTitle(title != null ? title : R.getI18NString("service.gui.CERT_DIALOG_TITLE")); - // for now shows only the first certificate from the chain - this.cert = certs[0]; + this.certs = certs; this.message = message; setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); @@ -124,16 +124,17 @@ private void init() this.getContentPane().add(contentPane, BorderLayout.CENTER); Component certInfoPane; - if(cert instanceof X509Certificate) + if(certs[0] instanceof X509Certificate) { - certInfoPane = new X509CertificatePanel((X509Certificate)cert); + certInfoPane = new X509CertificatePanel(Arrays.asList((X509Certificate[])certs)); } else { JTextArea textArea = new JTextArea(); textArea.setOpaque(false); textArea.setEditable(false); - textArea.setText(cert.toString()); + // for now shows only the first certificate from the chain + textArea.setText(certs[0].toString()); certInfoPane = textArea; } diff --git a/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java b/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java index dde05a449..027f03044 100644 --- a/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java +++ b/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java @@ -29,11 +29,24 @@ public class X509CertificatePanel private static final long serialVersionUID = -8368302061995971947L; /** - * Constructs a X509 certificate panel. + * Constructs a X509 certificate panel from a single certificate. + * If a chain is available instead use the second constructor. + * This constructor is kept for backwards compatibility and for convenience + * when there is only one certificate of interest. * * @param certificate X509Certificate object */ public X509CertificatePanel(X509Certificate certificate) + { + this(Arrays.asList(certificate)); + } + + /** + * Constructs a X509 certificate panel. + * + * @param certificates X509Certificate objects + */ + public X509CertificatePanel(java.util.List certificates) { ResourceManagementService R = DesktopUtilActivator.getResources(); DateFormat dateFormatter @@ -55,6 +68,7 @@ public X509CertificatePanel(X509Certificate certificate) constraints.weighty = 0; constraints.gridy = currentRow++; + X509Certificate certificate = certificates.get(0); X500Principal issuer = certificate.getIssuerX500Principal(); X500Principal subject = certificate.getSubjectX500Principal(); From 4bea72d4814eb2a8216dd6d614a88fed579b391f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Kil=C3=A5s?= Date: Sat, 2 Aug 2014 14:30:55 +0200 Subject: [PATCH 2/7] Improved X509CertificatePanel with support for displaying multiple certificates. --- resources/languages/resources.properties | 12 +- .../desktoputil/X509CertificatePanel.java | 478 +++++++++--------- 2 files changed, 234 insertions(+), 256 deletions(-) diff --git a/resources/languages/resources.properties b/resources/languages/resources.properties index 8ba6d1bda..211d07d5f 100644 --- a/resources/languages/resources.properties +++ b/resources/languages/resources.properties @@ -720,23 +720,23 @@ identity cannot
be automatically verified. \ Do you want to continue connecting?

\ For more information, click "Show Certificate". service.gui.CONTINUE_ANYWAY=Continue anyway -service.gui.CERT_INFO_ISSUED_TO=Issued To +service.gui.CERT_INFO_ISSUED_TO=Issued To service.gui.CERT_INFO_CN=Common Name: service.gui.CERT_INFO_O=Organization: service.gui.CERT_INFO_C=Country Name: service.gui.CERT_INFO_ST=State or Province Name: service.gui.CERT_INFO_L=Locality Name: -service.gui.CERT_INFO_ISSUED_BY=Issued By +service.gui.CERT_INFO_ISSUED_BY=Issued By service.gui.CERT_INFO_OU=Organizational Unit: -service.gui.CERT_INFO_VALIDITY=Validity +service.gui.CERT_INFO_VALIDITY=Validity service.gui.CERT_INFO_ISSUED_ON=Issued On: service.gui.CERT_INFO_EXPIRES_ON=Expires On: -service.gui.CERT_INFO_FINGERPRINTS=Fingerprints -service.gui.CERT_INFO_CERT_DETAILS=Certificate Info +service.gui.CERT_INFO_FINGERPRINTS=Fingerprints +service.gui.CERT_INFO_CERT_DETAILS=Certificate Info service.gui.CERT_INFO_SER_NUM=Serial Number: service.gui.CERT_INFO_VER=Version: service.gui.CERT_INFO_SIGN_ALG=Signature Algorithm: -service.gui.CERT_INFO_PUB_KEY_INFO=Public Key Info +service.gui.CERT_INFO_PUB_KEY_INFO=Public Key Info service.gui.CERT_INFO_ALG=Algorithm: service.gui.CERT_INFO_PUB_KEY=Public Key: service.gui.CERT_INFO_KEY_BYTES_PRINT={0} bytes: {1} diff --git a/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java b/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java index 027f03044..6558bb914 100644 --- a/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java +++ b/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java @@ -10,13 +10,16 @@ import java.security.*; import java.security.cert.*; import java.security.interfaces.*; -import java.text.*; import java.util.*; import javax.naming.*; import javax.naming.ldap.*; import javax.security.auth.x500.*; import javax.swing.*; +import javax.swing.border.*; +import javax.swing.event.*; +import javax.swing.text.*; +import javax.swing.tree.*; import org.jitsi.service.resources.*; @@ -28,6 +31,8 @@ public class X509CertificatePanel { private static final long serialVersionUID = -8368302061995971947L; + private final JEditorPane infoTextPane = new JEditorPane(); + /** * Constructs a X509 certificate panel from a single certificate. * If a chain is available instead use the second constructor. @@ -48,36 +53,100 @@ public X509CertificatePanel(X509Certificate certificate) */ public X509CertificatePanel(java.util.List certificates) { - ResourceManagementService R = DesktopUtilActivator.getResources(); - DateFormat dateFormatter - = DateFormat.getDateInstance(DateFormat.MEDIUM); + setLayout(new BorderLayout(5, 5)); + + // Certificate chain list + TransparentPanel topPanel = new TransparentPanel(new BorderLayout()); + topPanel.add(new JLabel( + "Certificate chain:"), + BorderLayout.NORTH); + + DefaultMutableTreeNode top = new DefaultMutableTreeNode(); + DefaultMutableTreeNode previous = top; + ListIterator it = certificates.listIterator( + certificates.size()); + while (it.hasPrevious()) { + X509Certificate cert = it.previous(); + DefaultMutableTreeNode next = new DefaultMutableTreeNode(cert); + previous.add(next); + previous = next; + } + JTree tree = new JTree(top); + tree.setBorder(new BevelBorder(BevelBorder.LOWERED)); + tree.setRootVisible(false); + tree.setExpandsSelectedPaths(true); + tree.getSelectionModel().setSelectionMode( + TreeSelectionModel.SINGLE_TREE_SELECTION); + tree.setCellRenderer(new DefaultTreeCellRenderer() { + + @Override + public Component getTreeCellRendererComponent(JTree tree, + Object value, boolean sel, boolean expanded, boolean leaf, + int row, boolean hasFocus) { + JLabel component = (JLabel) super.getTreeCellRendererComponent( + tree, value, sel, expanded, leaf, row, hasFocus); + if (value instanceof DefaultMutableTreeNode) { + Object o = ((DefaultMutableTreeNode) value).getUserObject(); + if (o instanceof X509Certificate) { + component.setText( + getSimplifiedName((X509Certificate) o)); + } + } + return component; + } - Insets valueInsets = new Insets(2,10,0,0); - Insets titleInsets = new Insets(10,5,0,0); + }); + tree.getSelectionModel().addTreeSelectionListener( + new TreeSelectionListener() + { - setLayout(new GridBagLayout()); + @Override + public void valueChanged(TreeSelectionEvent e) { + valueChangedPerformed(e); + } + }); + tree.setSelectionPath(new TreePath((( + (DefaultTreeModel)tree.getModel()).getPathToRoot(previous)))); + topPanel.add(tree, BorderLayout.CENTER); - int currentRow = 0; + add(topPanel, BorderLayout.NORTH); - GridBagConstraints constraints = new GridBagConstraints(); - constraints.anchor = GridBagConstraints.WEST; - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.insets = new Insets(2,5,0,0); - constraints.gridx = 0; - constraints.weightx = 0; - constraints.weighty = 0; - constraints.gridy = currentRow++; + // Certificate details pane + Caret caret = infoTextPane.getCaret(); + if (caret instanceof DefaultCaret) + { + ((DefaultCaret) caret).setUpdatePolicy(DefaultCaret.NEVER_UPDATE); + } - X509Certificate certificate = certificates.get(0); + /* + * Make JEditorPane respect our default font because we will be using it + * to just display text. + */ + infoTextPane.putClientProperty( + JEditorPane.HONOR_DISPLAY_PROPERTIES, + true); + + infoTextPane.setOpaque(false); + infoTextPane.setEditable(false); + infoTextPane.setContentType("text/html"); + infoTextPane.setText(toString(certificates.get(0))); + + final JScrollPane certScroll = new JScrollPane(infoTextPane); + certScroll.setPreferredSize(new Dimension(300, 500)); + add(certScroll, BorderLayout.CENTER); + } + + private String toString(X509Certificate certificate) + { + final StringBuilder sb = new StringBuilder(); + ResourceManagementService R = DesktopUtilActivator.getResources(); X500Principal issuer = certificate.getIssuerX500Principal(); X500Principal subject = certificate.getSubjectX500Principal(); - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_ISSUED_TO")), - constraints); + sb.append("\n"); // subject - constraints.insets = valueInsets; + addTitle(sb, R.getI18NString("service.gui.CERT_INFO_ISSUED_TO")); try { for(Rdn name : new LdapName(subject.getName()).getRdns()) @@ -89,47 +158,31 @@ public X509CertificatePanel(java.util.List certificates) if ((lbl == null) || ("!" + lblKey + "!").equals(lbl)) lbl = nameType; - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel(lbl), constraints); - + final String value; Object nameValue = name.getValue(); if (nameValue instanceof byte[]) { byte[] nameValueAsByteArray = (byte[]) nameValue; - lbl + value = getHex(nameValueAsByteArray) + " (" + new String(nameValueAsByteArray) + ")"; } else - lbl = nameValue.toString(); + value = nameValue.toString(); - constraints.gridx = 1; - add(new JLabel(lbl), constraints); + addField(sb, lbl, value); } } catch (InvalidNameException ine) { - constraints.gridy = currentRow++; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_CN")), - constraints); - constraints.gridx = 1; - add( - new JLabel(subject.getName()), - constraints); + addField(sb, R.getI18NString("service.gui.CERT_INFO_CN"), + subject.getName()); } // issuer - constraints.gridy = currentRow++; - constraints.gridx = 0; - constraints.insets = titleInsets; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_ISSUED_BY")), - constraints); - constraints.insets = valueInsets; + addTitle(sb, R.getI18NString("service.gui.CERT_INFO_ISSUED_BY")); try { for(Rdn name : new LdapName(issuer.getName()).getRdns()) @@ -141,268 +194,135 @@ public X509CertificatePanel(java.util.List certificates) if ((lbl == null) || ("!" + lblKey + "!").equals(lbl)) lbl = nameType; - constraints.gridy = currentRow++; - constraints.gridx = 0; - constraints.gridx = 0; - add(new JLabel(lbl), constraints); - + final String value; Object nameValue = name.getValue(); if (nameValue instanceof byte[]) { byte[] nameValueAsByteArray = (byte[]) nameValue; - lbl + value = getHex(nameValueAsByteArray) + " (" + new String(nameValueAsByteArray) + ")"; } else - lbl = nameValue.toString(); + value = nameValue.toString(); - constraints.gridx = 1; - add(new JLabel(lbl), constraints); + addField(sb, lbl, value); } } catch (InvalidNameException ine) { - constraints.gridy = currentRow++; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_CN")), - constraints); - constraints.gridx = 1; - add( - new JLabel(issuer.getName()), - constraints); + addField(sb, R.getI18NString("service.gui.CERT_INFO_CN"), + issuer.getName()); } // validity - constraints.gridy = currentRow++; - constraints.gridx = 0; - constraints.insets = titleInsets; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_VALIDITY")), - constraints); - constraints.insets = valueInsets; - - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_ISSUED_ON")), - constraints); - constraints.gridx = 1; - add( - new JLabel(dateFormatter.format(certificate.getNotBefore())), - constraints); - - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_EXPIRES_ON")), - constraints); - constraints.gridx = 1; - add( - new JLabel(dateFormatter.format(certificate.getNotAfter())), - constraints); - - constraints.gridy = currentRow++; - constraints.gridx = 0; - constraints.insets = titleInsets; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_FINGERPRINTS")), - constraints); - constraints.insets = valueInsets; + addTitle(sb, R.getI18NString("service.gui.CERT_INFO_VALIDITY")); + addField(sb, R.getI18NString("service.gui.CERT_INFO_ISSUED_ON"), + certificate.getNotBefore().toString()); + addField(sb, R.getI18NString("service.gui.CERT_INFO_EXPIRES_ON"), + certificate.getNotAfter().toString()); + addTitle(sb, R.getI18NString("service.gui.CERT_INFO_FINGERPRINTS")); try { String sha1String = getThumbprint(certificate, "SHA1"); String md5String = getThumbprint(certificate, "MD5"); - JTextArea sha1Area = new JTextArea(sha1String); - sha1Area.setLineWrap(false); - sha1Area.setOpaque(false); - sha1Area.setWrapStyleWord(true); - sha1Area.setEditable(false); - - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel("SHA1:"), - constraints); - - constraints.gridx = 1; - add( - sha1Area, - constraints); - - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel("MD5:"), - constraints); - - JTextArea md5Area = new JTextArea(md5String); - md5Area.setLineWrap(false); - md5Area.setOpaque(false); - md5Area.setWrapStyleWord(true); - md5Area.setEditable(false); - - constraints.gridx = 1; - add( - md5Area, - constraints); + addField(sb, "SHA1:", sha1String); + addField(sb, "MD5:", md5String); } - catch (Exception e) + catch (CertificateException e) { // do nothing as we cannot show this value } - constraints.gridy = currentRow++; - constraints.gridx = 0; - constraints.insets = titleInsets; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_CERT_DETAILS")), - constraints); - constraints.insets = valueInsets; - - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_SER_NUM")), - constraints); - constraints.gridx = 1; - add( - new JLabel(certificate.getSerialNumber().toString()), - constraints); - - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_VER")), - constraints); - constraints.gridx = 1; - add( - new JLabel(String.valueOf(certificate.getVersion())), - constraints); - - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_SIGN_ALG")), - constraints); - constraints.gridx = 1; - add( - new JLabel(String.valueOf(certificate.getSigAlgName())), - constraints); - - constraints.gridy = currentRow++; - constraints.gridx = 0; - constraints.insets = titleInsets; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_PUB_KEY_INFO")), - constraints); - constraints.insets = valueInsets; - - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_ALG")), - constraints); - constraints.gridx = 1; - add( - new JLabel(certificate.getPublicKey().getAlgorithm()), - constraints); + addTitle(sb, R.getI18NString("service.gui.CERT_INFO_CERT_DETAILS")); + + addField(sb, R.getI18NString("service.gui.CERT_INFO_SER_NUM"), + certificate.getSerialNumber().toString()); + + addField(sb, R.getI18NString("service.gui.CERT_INFO_VER"), + String.valueOf(certificate.getVersion())); + + addField(sb, R.getI18NString("service.gui.CERT_INFO_SIGN_ALG"), + String.valueOf(certificate.getSigAlgName())); + + addTitle(sb, R.getI18NString("service.gui.CERT_INFO_PUB_KEY_INFO")); + + addField(sb, R.getI18NString("service.gui.CERT_INFO_ALG"), + certificate.getPublicKey().getAlgorithm()); if(certificate.getPublicKey().getAlgorithm().equals("RSA")) { RSAPublicKey key = (RSAPublicKey)certificate.getPublicKey(); - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_PUB_KEY")), - constraints); - - JTextArea pubkeyArea = new JTextArea( + addField(sb, R.getI18NString("service.gui.CERT_INFO_PUB_KEY"), R.getI18NString( "service.gui.CERT_INFO_KEY_BYTES_PRINT", new String[]{ String.valueOf(key.getModulus().toByteArray().length-1), key.getModulus().toString(16) })); - pubkeyArea.setLineWrap(false); - pubkeyArea.setOpaque(false); - pubkeyArea.setWrapStyleWord(true); - pubkeyArea.setEditable(false); - - constraints.gridx = 1; - add( - pubkeyArea, - constraints); - - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_EXP")), - constraints); - constraints.gridx = 1; - add( - new JLabel(key.getPublicExponent().toString()), - constraints); - - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_KEY_SIZE")), - constraints); - constraints.gridx = 1; - add( - new JLabel(R.getI18NString( + + addField(sb, R.getI18NString("service.gui.CERT_INFO_EXP"), + key.getPublicExponent().toString()); + + addField(sb, R.getI18NString("service.gui.CERT_INFO_KEY_SIZE"), + R.getI18NString( "service.gui.CERT_INFO_KEY_BITS_PRINT", new String[]{ - String.valueOf(key.getModulus().bitLength())})), - constraints); + String.valueOf(key.getModulus().bitLength())})); } else if(certificate.getPublicKey().getAlgorithm().equals("DSA")) { DSAPublicKey key = (DSAPublicKey)certificate.getPublicKey(); - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel("Y:"), constraints); - - JTextArea yArea = new JTextArea(key.getY().toString(16)); - yArea.setLineWrap(false); - yArea.setOpaque(false); - yArea.setWrapStyleWord(true); - yArea.setEditable(false); - - constraints.gridx = 1; - add( - yArea, - constraints); + addField(sb, "Y:", key.getY().toString(16)); } - constraints.gridy = currentRow++; - constraints.gridx = 0; - add(new JLabel( - R.getI18NString("service.gui.CERT_INFO_SIGN")), - constraints); - - JTextArea signArea = new JTextArea( + addField(sb, R.getI18NString("service.gui.CERT_INFO_SIGN"), R.getI18NString( "service.gui.CERT_INFO_KEY_BYTES_PRINT", new String[]{ String.valueOf(certificate.getSignature().length), getHex(certificate.getSignature()) })); - signArea.setLineWrap(false); - signArea.setOpaque(false); - signArea.setWrapStyleWord(true); - signArea.setEditable(false); - - constraints.gridx = 1; - add( - signArea, - constraints); + + sb.append("
"); + + return sb.toString(); + } + + /** + * Add a title. + * + * @param sb StringBuilder to append to + * @param title to print + */ + private void addTitle(StringBuilder sb, String title) + { + sb.append("

") + .append(title).append("

\n"); + } + + /** + * Add a field. + * @param sb StringBuilder to append to + * @param field name of the certificate field + * @param value to print + */ + private void addField(StringBuilder sb, String field, String value) + { + sb.append("") + .append("") + .append(field).append("") + .append("").append(value).append("") + .append("\n"); } /** @@ -464,4 +384,62 @@ private static String getThumbprint(X509Certificate cert, String algorithm) } return sb.toString(); } + + /** + * Construct a "simplified name" based on the subject DN from the + * certificate. The purpose is to have something shorter to display in the + * list. The name used is one of the following DN parts, if + * available, otherwise the complete DN: + * 'CN', 'OU' or else 'O'. + * @param cert to read subject DN from + * @return the simplified name + */ + private static String getSimplifiedName(X509Certificate cert) { + final HashMap parts = new HashMap(); + try + { + for (Rdn name : new LdapName( + cert.getSubjectX500Principal().getName()).getRdns()) + { + if (name.getType() != null && name.getValue() != null) + { + parts.put(name.getType(), name.getValue().toString()); + } + } + } + catch (InvalidNameException ignored) {} // NOPMD + + String result = parts.get("CN"); + if (result == null) + { + result = parts.get("OU"); + } + if (result == null) { + result = parts.get("O"); + } + if (result == null) + { + result = cert.getSubjectX500Principal().getName(); + } + return result; + } + + /** + * Called when the selection changed in the tree. + * Loads the selected certificate. + * @param e the event + */ + private void valueChangedPerformed(TreeSelectionEvent e) + { + Object o = e.getNewLeadSelectionPath().getLastPathComponent(); + if (o instanceof DefaultMutableTreeNode) + { + DefaultMutableTreeNode node = (DefaultMutableTreeNode) o; + if (node.getUserObject() instanceof X509Certificate) + { + infoTextPane.setText( + toString((X509Certificate) node.getUserObject())); + } + } + } } From 8317166f2f2e6a2db7727e5ca5d2165adabaf7ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Kil=C3=A5s?= Date: Sat, 2 Aug 2014 14:36:31 +0200 Subject: [PATCH 3/7] Fixing formatting issue. --- .../communicator/plugin/desktoputil/ViewCertificateFrame.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/net/java/sip/communicator/plugin/desktoputil/ViewCertificateFrame.java b/src/net/java/sip/communicator/plugin/desktoputil/ViewCertificateFrame.java index 1afc95e06..a4067025d 100644 --- a/src/net/java/sip/communicator/plugin/desktoputil/ViewCertificateFrame.java +++ b/src/net/java/sip/communicator/plugin/desktoputil/ViewCertificateFrame.java @@ -124,7 +124,7 @@ private void init() this.getContentPane().add(contentPane, BorderLayout.CENTER); Component certInfoPane; - if(certs[0] instanceof X509Certificate) + if (certs[0] instanceof X509Certificate) { certInfoPane = new X509CertificatePanel(Arrays.asList((X509Certificate[])certs)); } From dfa09c310fefada02bbb839a5c598b447788fa2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Kil=C3=A5s?= Date: Sat, 2 Aug 2014 14:44:07 +0200 Subject: [PATCH 4/7] Missed one i18n string. --- resources/languages/resources.properties | 1 + .../plugin/desktoputil/X509CertificatePanel.java | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/resources/languages/resources.properties b/resources/languages/resources.properties index 211d07d5f..72562eb61 100644 --- a/resources/languages/resources.properties +++ b/resources/languages/resources.properties @@ -720,6 +720,7 @@ identity cannot
be automatically verified. \ Do you want to continue connecting?

\ For more information, click "Show Certificate". service.gui.CONTINUE_ANYWAY=Continue anyway +service.gui.CERT_INFO_CHAIN=Certificate Chain: service.gui.CERT_INFO_ISSUED_TO=Issued To service.gui.CERT_INFO_CN=Common Name: service.gui.CERT_INFO_O=Organization: diff --git a/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java b/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java index 6558bb914..6d5bacf8f 100644 --- a/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java +++ b/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java @@ -33,6 +33,9 @@ public class X509CertificatePanel private final JEditorPane infoTextPane = new JEditorPane(); + private final ResourceManagementService R + = DesktopUtilActivator.getResources(); + /** * Constructs a X509 certificate panel from a single certificate. * If a chain is available instead use the second constructor. @@ -57,9 +60,9 @@ public X509CertificatePanel(java.util.List certificates) // Certificate chain list TransparentPanel topPanel = new TransparentPanel(new BorderLayout()); - topPanel.add(new JLabel( - "Certificate chain:"), - BorderLayout.NORTH); + topPanel.add(new JLabel("" + + R.getI18NString("service.gui.CERT_INFO_CHAIN") + + ""), BorderLayout.NORTH); DefaultMutableTreeNode top = new DefaultMutableTreeNode(); DefaultMutableTreeNode previous = top; @@ -139,7 +142,6 @@ public void valueChanged(TreeSelectionEvent e) { private String toString(X509Certificate certificate) { final StringBuilder sb = new StringBuilder(); - ResourceManagementService R = DesktopUtilActivator.getResources(); X500Principal issuer = certificate.getIssuerX500Principal(); X500Principal subject = certificate.getSubjectX500Principal(); From f97dccddf5f213474a8c551e7f5f471dc10f10aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Kil=C3=A5s?= Date: Sun, 3 Aug 2014 13:40:55 +0200 Subject: [PATCH 5/7] Changed from List to array type for X509CertificatePanel constructor --- .../desktoputil/VerifyCertificateDialogImpl.java | 3 ++- .../plugin/desktoputil/ViewCertificateFrame.java | 13 +++++++++++-- .../plugin/desktoputil/X509CertificatePanel.java | 16 +++++++++------- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/net/java/sip/communicator/plugin/desktoputil/VerifyCertificateDialogImpl.java b/src/net/java/sip/communicator/plugin/desktoputil/VerifyCertificateDialogImpl.java index 6fba19222..71b6c68ad 100644 --- a/src/net/java/sip/communicator/plugin/desktoputil/VerifyCertificateDialogImpl.java +++ b/src/net/java/sip/communicator/plugin/desktoputil/VerifyCertificateDialogImpl.java @@ -242,7 +242,8 @@ private void actionShowCertificate() Component certInfoPane = null; if (!certs.isEmpty()) { - certInfoPane = new X509CertificatePanel(certs); + certInfoPane = new X509CertificatePanel( + certs.toArray(new X509Certificate[0])); } else { diff --git a/src/net/java/sip/communicator/plugin/desktoputil/ViewCertificateFrame.java b/src/net/java/sip/communicator/plugin/desktoputil/ViewCertificateFrame.java index a4067025d..3c2d9d55e 100644 --- a/src/net/java/sip/communicator/plugin/desktoputil/ViewCertificateFrame.java +++ b/src/net/java/sip/communicator/plugin/desktoputil/ViewCertificateFrame.java @@ -8,7 +8,7 @@ import java.awt.*; import java.security.cert.*; -import java.util.Arrays; +import java.util.*; import javax.swing.*; import org.jitsi.service.resources.*; @@ -126,7 +126,16 @@ private void init() Component certInfoPane; if (certs[0] instanceof X509Certificate) { - certInfoPane = new X509CertificatePanel(Arrays.asList((X509Certificate[])certs)); + ArrayList x509s = new ArrayList(); + for (Certificate c : certs) + { + if (c instanceof X509Certificate) + { + x509s.add(c); + } + } + certInfoPane = new X509CertificatePanel( + (X509Certificate[]) x509s.toArray(new X509Certificate[0])); } else { diff --git a/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java b/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java index 6d5bacf8f..498a4ad9b 100644 --- a/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java +++ b/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java @@ -46,7 +46,10 @@ public class X509CertificatePanel */ public X509CertificatePanel(X509Certificate certificate) { - this(Arrays.asList(certificate)); + this(new X509Certificate[] + { + certificate + }); } /** @@ -54,7 +57,7 @@ public X509CertificatePanel(X509Certificate certificate) * * @param certificates X509Certificate objects */ - public X509CertificatePanel(java.util.List certificates) + public X509CertificatePanel(X509Certificate[] certificates) { setLayout(new BorderLayout(5, 5)); @@ -66,10 +69,9 @@ public X509CertificatePanel(java.util.List certificates) DefaultMutableTreeNode top = new DefaultMutableTreeNode(); DefaultMutableTreeNode previous = top; - ListIterator it = certificates.listIterator( - certificates.size()); - while (it.hasPrevious()) { - X509Certificate cert = it.previous(); + for (int i = certificates.length - 1; i >= 0; i--) + { + X509Certificate cert = certificates[i]; DefaultMutableTreeNode next = new DefaultMutableTreeNode(cert); previous.add(next); previous = next; @@ -132,7 +134,7 @@ public void valueChanged(TreeSelectionEvent e) { infoTextPane.setOpaque(false); infoTextPane.setEditable(false); infoTextPane.setContentType("text/html"); - infoTextPane.setText(toString(certificates.get(0))); + infoTextPane.setText(toString(certificates[0])); final JScrollPane certScroll = new JScrollPane(infoTextPane); certScroll.setPreferredSize(new Dimension(300, 500)); From 0d91f834c561790bdb1f7e1f86ef2f5c10f8c91e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Kil=C3=A5s?= Date: Sun, 3 Aug 2014 13:49:55 +0200 Subject: [PATCH 6/7] Curly braces on new lines --- .../desktoputil/X509CertificatePanel.java | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java b/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java index 498a4ad9b..630487fe4 100644 --- a/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java +++ b/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java @@ -82,17 +82,21 @@ public X509CertificatePanel(X509Certificate[] certificates) tree.setExpandsSelectedPaths(true); tree.getSelectionModel().setSelectionMode( TreeSelectionModel.SINGLE_TREE_SELECTION); - tree.setCellRenderer(new DefaultTreeCellRenderer() { + tree.setCellRenderer(new DefaultTreeCellRenderer() + { @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, - int row, boolean hasFocus) { + int row, boolean hasFocus) + { JLabel component = (JLabel) super.getTreeCellRendererComponent( tree, value, sel, expanded, leaf, row, hasFocus); - if (value instanceof DefaultMutableTreeNode) { + if (value instanceof DefaultMutableTreeNode) + { Object o = ((DefaultMutableTreeNode) value).getUserObject(); - if (o instanceof X509Certificate) { + if (o instanceof X509Certificate) + { component.setText( getSimplifiedName((X509Certificate) o)); } @@ -106,7 +110,8 @@ public Component getTreeCellRendererComponent(JTree tree, { @Override - public void valueChanged(TreeSelectionEvent e) { + public void valueChanged(TreeSelectionEvent e) + { valueChangedPerformed(e); } }); @@ -398,7 +403,8 @@ private static String getThumbprint(X509Certificate cert, String algorithm) * @param cert to read subject DN from * @return the simplified name */ - private static String getSimplifiedName(X509Certificate cert) { + private static String getSimplifiedName(X509Certificate cert) + { final HashMap parts = new HashMap(); try { @@ -411,14 +417,17 @@ private static String getSimplifiedName(X509Certificate cert) { } } } - catch (InvalidNameException ignored) {} // NOPMD + catch (InvalidNameException ignored) // NOPMD + { + } String result = parts.get("CN"); if (result == null) { result = parts.get("OU"); } - if (result == null) { + if (result == null) + { result = parts.get("O"); } if (result == null) From ba1c0157c56c68dcf72650865b389e11a6d8f12e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Kil=C3=A5s?= Date: Wed, 6 Aug 2014 13:54:15 +0200 Subject: [PATCH 7/7] Expand scope of X509CertificatePanel to also handle other certificate types. Actually removes some duplicated and some unnecessary code. --- .../VerifyCertificateDialogImpl.java | 45 ++----------- .../desktoputil/ViewCertificateFrame.java | 38 +---------- .../desktoputil/X509CertificatePanel.java | 63 ++++++++++++++----- 3 files changed, 55 insertions(+), 91 deletions(-) diff --git a/src/net/java/sip/communicator/plugin/desktoputil/VerifyCertificateDialogImpl.java b/src/net/java/sip/communicator/plugin/desktoputil/VerifyCertificateDialogImpl.java index 71b6c68ad..ed09959e3 100644 --- a/src/net/java/sip/communicator/plugin/desktoputil/VerifyCertificateDialogImpl.java +++ b/src/net/java/sip/communicator/plugin/desktoputil/VerifyCertificateDialogImpl.java @@ -9,7 +9,6 @@ import java.awt.*; import java.awt.event.*; import java.security.cert.*; -import java.util.*; import javax.swing.*; import javax.swing.border.*; @@ -53,10 +52,9 @@ class VerifyCertificateDialogImpl private static final int MAX_MSG_PANE_HEIGHT = 800; /** - * The certificate to show. + * The certificates to show. */ - Certificate cert; - java.util.List certs; + Certificate[] certs; /** * A text that describes why the verification failed. @@ -113,16 +111,7 @@ public VerifyCertificateDialogImpl(Certificate[] certs, R.getI18NString("service.gui.CERT_DIALOG_TITLE")); setModal(true); - this.certs = new ArrayList(); - for (Certificate certificate : certs) - { - if (certificate instanceof X509Certificate) { - this.certs.add((X509Certificate) certificate); - } - } - // for now shows only the first certificate from the chain for - // non X.509 certificates - this.cert = certs[0]; + this.certs = certs; this.message = message; setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); @@ -239,33 +228,7 @@ private void actionShowCertificate() certPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); certPanel.add(alwaysTrustCheckBox, BorderLayout.NORTH); - Component certInfoPane = null; - if (!certs.isEmpty()) - { - certInfoPane = new X509CertificatePanel( - certs.toArray(new X509Certificate[0])); - } - else - { - JTextArea textArea = new JTextArea(); - textArea.setOpaque(false); - textArea.setEditable(false); - textArea.setText(cert.toString()); - - final JScrollPane certScroll = new JScrollPane(certInfoPane); - certScroll.setPreferredSize(new Dimension(300, 300)); - - SwingUtilities.invokeLater(new Runnable() - { - public void run() - { - certScroll.getVerticalScrollBar().setValue(0); - } - }); - - certInfoPane = certScroll; - } - certPanel.add(certInfoPane, BorderLayout.CENTER); + certPanel.add(new X509CertificatePanel(certs), BorderLayout.CENTER); certButton.setText(R.getI18NString("service.gui.HIDE_CERT")); diff --git a/src/net/java/sip/communicator/plugin/desktoputil/ViewCertificateFrame.java b/src/net/java/sip/communicator/plugin/desktoputil/ViewCertificateFrame.java index 3c2d9d55e..943a20401 100644 --- a/src/net/java/sip/communicator/plugin/desktoputil/ViewCertificateFrame.java +++ b/src/net/java/sip/communicator/plugin/desktoputil/ViewCertificateFrame.java @@ -8,7 +8,6 @@ import java.awt.*; import java.security.cert.*; -import java.util.*; import javax.swing.*; import org.jitsi.service.resources.*; @@ -123,41 +122,8 @@ private void init() this.getContentPane().add(contentPane, BorderLayout.CENTER); - Component certInfoPane; - if (certs[0] instanceof X509Certificate) - { - ArrayList x509s = new ArrayList(); - for (Certificate c : certs) - { - if (c instanceof X509Certificate) - { - x509s.add(c); - } - } - certInfoPane = new X509CertificatePanel( - (X509Certificate[]) x509s.toArray(new X509Certificate[0])); - } - else - { - JTextArea textArea = new JTextArea(); - textArea.setOpaque(false); - textArea.setEditable(false); - // for now shows only the first certificate from the chain - textArea.setText(certs[0].toString()); - certInfoPane = textArea; - } - - final JScrollPane certScroll = new JScrollPane(certInfoPane); - certScroll.setPreferredSize(new Dimension(300, 600)); - certPanel.add(certScroll, BorderLayout.CENTER); - - SwingUtilities.invokeLater(new Runnable() - { - public void run() - { - certScroll.getVerticalScrollBar().setValue(0); - } - }); + certPanel.add(new X509CertificatePanel(certs), BorderLayout.CENTER); + setPreferredSize(null); pack(); diff --git a/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java b/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java index 630487fe4..ff4865113 100644 --- a/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java +++ b/src/net/java/sip/communicator/plugin/desktoputil/X509CertificatePanel.java @@ -9,6 +9,7 @@ import java.awt.*; import java.security.*; import java.security.cert.*; +import java.security.cert.Certificate; import java.security.interfaces.*; import java.util.*; @@ -44,9 +45,9 @@ public class X509CertificatePanel * * @param certificate X509Certificate object */ - public X509CertificatePanel(X509Certificate certificate) + public X509CertificatePanel(Certificate certificate) { - this(new X509Certificate[] + this(new Certificate[] { certificate }); @@ -57,7 +58,7 @@ public X509CertificatePanel(X509Certificate certificate) * * @param certificates X509Certificate objects */ - public X509CertificatePanel(X509Certificate[] certificates) + public X509CertificatePanel(Certificate[] certificates) { setLayout(new BorderLayout(5, 5)); @@ -71,7 +72,7 @@ public X509CertificatePanel(X509Certificate[] certificates) DefaultMutableTreeNode previous = top; for (int i = certificates.length - 1; i >= 0; i--) { - X509Certificate cert = certificates[i]; + Certificate cert = certificates[i]; DefaultMutableTreeNode next = new DefaultMutableTreeNode(cert); previous.add(next); previous = next; @@ -100,6 +101,17 @@ public Component getTreeCellRendererComponent(JTree tree, component.setText( getSimplifiedName((X509Certificate) o)); } + else + { + // We don't know how to represent this certificate type, + // let's use the first 20 characters + String text = o.toString(); + if (text.length() > 20) + { + text = text.substring(0, 20); + } + component.setText(text); + } } return component; } @@ -146,13 +158,42 @@ public void valueChanged(TreeSelectionEvent e) add(certScroll, BorderLayout.CENTER); } - private String toString(X509Certificate certificate) + /** + * Creates a String representation of the given object. + * @param certificate to print + * @return the String representation + */ + private String toString(Object certificate) { final StringBuilder sb = new StringBuilder(); + sb.append("\n"); + + if (certificate instanceof X509Certificate) + { + renderX509(sb, (X509Certificate) certificate); + } + else + { + sb.append("
\n");
+            sb.append(certificate.toString());
+            sb.append("
\n"); + } + + sb.append(""); + return sb.toString(); + } + + /** + * Appends an HTML representation of the given X509Certificate. + * @param sb StringBuilder to append to + * @param certificate to print + */ + private void renderX509(StringBuilder sb, X509Certificate certificate) + { X500Principal issuer = certificate.getIssuerX500Principal(); X500Principal subject = certificate.getSubjectX500Principal(); - sb.append("\n"); + sb.append("
\n"); // subject addTitle(sb, R.getI18NString("service.gui.CERT_INFO_ISSUED_TO")); @@ -300,9 +341,7 @@ else if(certificate.getPublicKey().getAlgorithm().equals("DSA")) getHex(certificate.getSignature()) })); - sb.append("
"); - - return sb.toString(); + sb.append("\n"); } /** @@ -448,11 +487,7 @@ private void valueChangedPerformed(TreeSelectionEvent e) if (o instanceof DefaultMutableTreeNode) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) o; - if (node.getUserObject() instanceof X509Certificate) - { - infoTextPane.setText( - toString((X509Certificate) node.getUserObject())); - } + infoTextPane.setText(toString(node.getUserObject())); } } }