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] 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())); + } + } + } }