/* * DViewCRL.java * This file is part of Portecle, a multipurpose keystore and certificate tool. * * Copyright © 2004 Wayne Grant, [email protected] * 2008-2009 Ville Skyttä, [email protected] * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ package net.sf.portecle; import static net.sf.portecle.FPortecle.RB; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.FileNotFoundException; import java.math.BigInteger; import java.security.cert.X509CRL; import java.security.cert.X509CRLEntry; import java.text.DateFormat; import java.text.MessageFormat; import java.util.Collections; import java.util.Date; import java.util.Set; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.ListSelectionModel; import javax.swing.ScrollPaneConstants; import javax.swing.border.CompoundBorder; import javax.swing.border.EmptyBorder; import javax.swing.border.EtchedBorder; import javax.swing.border.TitledBorder; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.TableColumn; import net.sf.portecle.crypto.X509CertUtil; import net.sf.portecle.gui.SwingHelper; import net.sf.portecle.gui.error.DThrowable; /** * Modal dialog to display the details of a Certificate Revocation List (CRL). */ final class DViewCRL extends PortecleJDialog { /** CRL Version text field */ private JTextField m_jtfVersion; /** CRL Issuer text field */ private JTextField m_jtfIssuer; /** CRL EffectiveDate text field */ private JTextField m_jtfEffectiveDate; /** CRL Next Update text field */ private JTextField m_jtfNextUpdate; /** CRL Signature Algorithm text field */ private JTextField m_jtfSignatureAlgorithm; /** Button used to display the CRL's extensions */ private JButton m_jbCrlExtensions; /** Revoked Certificates table */ private JTable m_jtRevokedCerts; /** Button used to display the CRL's entries' extensions */ private JButton m_jbCrlEntryExtensions; /** Stores CRL to display */ private final X509CRL m_crl; /** * Creates new DViewCRL dialog. * * @param parent Parent window * @param sTitle The dialog title * @param crl CRL to display */ private DViewCRL(Window parent, String sTitle, X509CRL crl) { super(parent, sTitle, true); m_crl = crl; initComponents(); } /** * Create, show, and wait for a new DViewCRL dialog. * * @param parent Parent window * @param url URL, URI or file to load CRL from * @return whether the dialog was successfully opened */ public static boolean showAndWait(Window parent, Object url) { String title = MessageFormat.format(RB.getString("FPortecle.CrlDetails.Title"), url); DViewCRL dialog; try { X509CRL crl = X509CertUtil.loadCRL(NetUtil.toURL(url)); dialog = new DViewCRL(parent, title, crl); } catch (FileNotFoundException ex) { JOptionPane.showMessageDialog(parent, MessageFormat.format(RB.getString("FPortecle.NoRead.message"), url), title, JOptionPane.WARNING_MESSAGE); return false; } catch (Exception ex) { DThrowable.showAndWait(parent, null, ex); return false; } dialog.setLocationRelativeTo(parent); SwingHelper.showAndWait(dialog); return true; } /** * Initialize the dialog's GUI components. */ private void initComponents() { // CRL Details: // Grid Bag Constraints templates for labels and text fields of CRL details GridBagConstraints gbcLbl = new GridBagConstraints(); gbcLbl.gridx = 0; gbcLbl.gridwidth = 1; gbcLbl.gridheight = 1; gbcLbl.insets = new Insets(5, 5, 5, 5); gbcLbl.anchor = GridBagConstraints.EAST; GridBagConstraints gbcTf = new GridBagConstraints(); gbcTf.gridx = 1; gbcTf.gridwidth = 1; gbcTf.gridheight = 1; gbcTf.insets = new Insets(5, 5, 5, 5); gbcTf.anchor = GridBagConstraints.WEST; // Version JLabel jlVersion = new JLabel(RB.getString("DViewCRL.jlVersion.text")); GridBagConstraints gbc_jlVersion = (GridBagConstraints) gbcLbl.clone(); gbc_jlVersion.gridy = 0; m_jtfVersion = new JTextField(3); m_jtfVersion.setEditable(false); m_jtfVersion.setToolTipText(RB.getString("DViewCRL.m_jtfVersion.tooltip")); jlVersion.setLabelFor(m_jtfVersion); GridBagConstraints gbc_jtfVersion = (GridBagConstraints) gbcTf.clone(); gbc_jtfVersion.gridy = 0; // Issuer JLabel jlIssuer = new JLabel(RB.getString("DViewCRL.jlIssuer.text")); GridBagConstraints gbc_jlIssuer = (GridBagConstraints) gbcLbl.clone(); gbc_jlIssuer.gridy = 1; m_jtfIssuer = new JTextField(40); m_jtfIssuer.setEditable(false); m_jtfIssuer.setToolTipText(RB.getString("DViewCRL.m_jtfIssuer.tooltip")); jlIssuer.setLabelFor(m_jtfIssuer); GridBagConstraints gbc_jtfIssuer = (GridBagConstraints) gbcTf.clone(); gbc_jtfIssuer.gridy = 1; // Effective Date JLabel jlEffectiveDate = new JLabel(RB.getString("DViewCRL.jlEffectiveDate.text")); GridBagConstraints gbc_jlEffectiveDate = (GridBagConstraints) gbcLbl.clone(); gbc_jlEffectiveDate.gridy = 2; m_jtfEffectiveDate = new JTextField(30); m_jtfEffectiveDate.setEditable(false); m_jtfEffectiveDate.setToolTipText(RB.getString("DViewCRL.m_jtfEffectiveDate.tooltip")); jlEffectiveDate.setLabelFor(m_jtfEffectiveDate); GridBagConstraints gbc_jtfEffectiveDate = (GridBagConstraints) gbcTf.clone(); gbc_jtfEffectiveDate.gridy = 2; // Next Update JLabel jlNextUpdate = new JLabel(RB.getString("DViewCRL.jlNextUpdate.text")); GridBagConstraints gbc_jlNextUpdate = (GridBagConstraints) gbcLbl.clone(); gbc_jlNextUpdate.gridy = 3; m_jtfNextUpdate = new JTextField(30); m_jtfNextUpdate.setEditable(false); m_jtfNextUpdate.setToolTipText(RB.getString("DViewCRL.m_jtfNextUpdate.tooltip")); jlNextUpdate.setLabelFor(m_jtfNextUpdate); GridBagConstraints gbc_jtfNextUpdate = (GridBagConstraints) gbcTf.clone(); gbc_jtfNextUpdate.gridy = 3; // Signature Algorithm JLabel jlSignatureAlgorithm = new JLabel(RB.getString("DViewCRL.jlSignatureAlgorithm.text")); GridBagConstraints gbc_jlSignatureAlgorithm = (GridBagConstraints) gbcLbl.clone(); gbc_jlSignatureAlgorithm.gridy = 4; m_jtfSignatureAlgorithm = new JTextField(15); m_jtfSignatureAlgorithm.setEditable(false); m_jtfSignatureAlgorithm.setToolTipText(RB.getString("DViewCRL.m_jtfSignatureAlgorithm.tooltip")); jlSignatureAlgorithm.setLabelFor(m_jtfSignatureAlgorithm); GridBagConstraints gbc_jtfSignatureAlgorithm = (GridBagConstraints) gbcTf.clone(); gbc_jtfSignatureAlgorithm.gridy = 4; // CRL Extensions m_jbCrlExtensions = new JButton(RB.getString("DViewCRL.m_jbCrlExtensions.text")); m_jbCrlExtensions.setMnemonic(RB.getString("DViewCRL.m_jbCrlExtensions.mnemonic").charAt(0)); m_jbCrlExtensions.setToolTipText(RB.getString("DViewCRL.m_jbCrlExtensions.tooltip")); m_jbCrlExtensions.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent evt) { crlExtensionsPressed(); } }); GridBagConstraints gbc_jbExtensions = new GridBagConstraints(); gbc_jbExtensions.gridx = 0; gbc_jbExtensions.gridy = 5; gbc_jbExtensions.gridwidth = 2; gbc_jbExtensions.gridheight = 1; gbc_jbExtensions.insets = new Insets(5, 5, 5, 5); gbc_jbExtensions.anchor = GridBagConstraints.EAST; // Revoked certificates table // Create the table using the appropriate table model RevokedCertsTableModel rcModel = new RevokedCertsTableModel(); m_jtRevokedCerts = new JTable(rcModel); m_jtRevokedCerts.setShowGrid(false); m_jtRevokedCerts.setRowMargin(0); m_jtRevokedCerts.getColumnModel().setColumnMargin(0); m_jtRevokedCerts.getTableHeader().setReorderingAllowed(false); m_jtRevokedCerts.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); // Add custom renderers for the table cells and headers for (int iCnt = 0; iCnt < m_jtRevokedCerts.getColumnCount(); iCnt++) { TableColumn column = m_jtRevokedCerts.getColumnModel().getColumn(iCnt); if (iCnt == 0) { column.setPreferredWidth(150); } column.setHeaderRenderer(new RevokedCertsTableHeadRend()); column.setCellRenderer(new RevokedCertsTableCellRend(m_jtRevokedCerts)); } ListSelectionModel listSelectionModel = m_jtRevokedCerts.getSelectionModel(); listSelectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); listSelectionModel.addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent evt) { // Ignore spurious events if (!evt.getValueIsAdjusting()) { crlEntrySelection(); } } }); // Make the table sortable m_jtRevokedCerts.setAutoCreateRowSorter(true); // ...and sort it by serial number by default m_jtRevokedCerts.getRowSorter().toggleSortOrder(0); // Put the table into a scroll pane JScrollPane jspRevokedCertsTable = new JScrollPane(m_jtRevokedCerts, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); jspRevokedCertsTable.getViewport().setBackground(m_jtRevokedCerts.getBackground()); // Put the scroll pane into a panel JPanel jpRevokedCertsTable = new JPanel(new BorderLayout(10, 10)); // More for the benefit of a reduced height jpRevokedCertsTable.setPreferredSize(new Dimension(100, 200)); jpRevokedCertsTable.add(jspRevokedCertsTable, BorderLayout.CENTER); // CRL Entry Extensions m_jbCrlEntryExtensions = new JButton(RB.getString("DViewCRL.m_jbCrlEntryExtensions.text")); m_jbCrlEntryExtensions.setMnemonic(RB.getString("DViewCRL.m_jbCrlEntryExtensions.mnemonic").charAt(0)); m_jbCrlEntryExtensions.setToolTipText(RB.getString("DViewCRL.m_jbCrlEntryExtensions.tooltip")); m_jbCrlEntryExtensions.setEnabled(false); m_jbCrlEntryExtensions.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent evt) { crlEntryExtensionsPressed(); } }); JPanel jpCrlEntryExtensions = new JPanel(new FlowLayout(FlowLayout.RIGHT)); jpCrlEntryExtensions.add(m_jbCrlEntryExtensions); jpRevokedCertsTable.add(jpCrlEntryExtensions, BorderLayout.SOUTH); GridBagConstraints gbc_jpRevokedCertsTable = new GridBagConstraints(); gbc_jpRevokedCertsTable.gridx = 0; gbc_jpRevokedCertsTable.gridy = 6; gbc_jpRevokedCertsTable.gridwidth = 2; gbc_jpRevokedCertsTable.gridheight = 1; gbc_jpRevokedCertsTable.insets = new Insets(5, 5, 5, 5); gbc_jpRevokedCertsTable.fill = GridBagConstraints.BOTH; gbc_jpRevokedCertsTable.anchor = GridBagConstraints.CENTER; JPanel jpCRL = new JPanel(new GridBagLayout()); jpCRL.setBorder(new CompoundBorder(new EmptyBorder(10, 10, 10, 10), new EtchedBorder())); // Put it all together jpCRL.add(jlVersion, gbc_jlVersion); jpCRL.add(m_jtfVersion, gbc_jtfVersion); jpCRL.add(jlIssuer, gbc_jlIssuer); jpCRL.add(m_jtfIssuer, gbc_jtfIssuer); jpCRL.add(jlEffectiveDate, gbc_jlEffectiveDate); jpCRL.add(m_jtfEffectiveDate, gbc_jtfEffectiveDate); jpCRL.add(jlNextUpdate, gbc_jlNextUpdate); jpCRL.add(m_jtfNextUpdate, gbc_jtfNextUpdate); jpCRL.add(jlSignatureAlgorithm, gbc_jlSignatureAlgorithm); jpCRL.add(m_jtfSignatureAlgorithm, gbc_jtfSignatureAlgorithm); jpCRL.add(m_jbCrlExtensions, gbc_jbExtensions); jpCRL.add(jpRevokedCertsTable, gbc_jpRevokedCertsTable); // Populate the dialog with the CRL populateDialog(); // Add border with number of entries in CRL jpRevokedCertsTable.setBorder(new CompoundBorder( new TitledBorder(new EtchedBorder(), MessageFormat.format(RB.getString("DViewCRL.TableTitle"), m_jtRevokedCerts.getRowCount())), new EmptyBorder(5, 5, 5, 5))); // OK button JPanel jpOK = new JPanel(new FlowLayout(FlowLayout.CENTER)); JButton jbOK = getOkButton(true); jpOK.add(jbOK); // Put it all together getContentPane().add(jpCRL, BorderLayout.CENTER); getContentPane().add(jpOK, BorderLayout.SOUTH); getRootPane().setDefaultButton(jbOK); initDialog(); jbOK.requestFocusInWindow(); } /** * Populate the dialog with the CRL's details. */ private void populateDialog() { // Populate CRL fields: // Has the CRL [been issued/been updated] Date currentDate = new Date(); Date effectiveDate = m_crl.getThisUpdate(); boolean bEffective = currentDate.before(effectiveDate); // Version m_jtfVersion.setText(Integer.toString(m_crl.getVersion())); m_jtfVersion.setCaretPosition(0); // Issuer m_jtfIssuer.setText(m_crl.getIssuerDN().toString()); m_jtfIssuer.setCaretPosition(0); // Effective Date (include time zone) m_jtfEffectiveDate.setText( DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(effectiveDate)); if (bEffective) { m_jtfEffectiveDate.setText(MessageFormat.format( RB.getString("DViewCRL.m_jtfEffectiveDate.noteffective.text"), m_jtfEffectiveDate.getText())); m_jtfEffectiveDate.setForeground(Color.red); } else { m_jtfEffectiveDate.setForeground(m_jtfVersion.getForeground()); } m_jtfEffectiveDate.setCaretPosition(0); // Next update Date updateDate = m_crl.getNextUpdate(); if (updateDate != null) { m_jtfNextUpdate.setText( DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(updateDate)); if (currentDate.after(updateDate)) { m_jtfNextUpdate.setText(MessageFormat.format( RB.getString("DViewCRL.m_jtfNextUpdate.updateavailable.text"), m_jtfNextUpdate.getText())); m_jtfNextUpdate.setForeground(Color.red); } else { m_jtfNextUpdate.setForeground(m_jtfVersion.getForeground()); } } else { m_jtfNextUpdate.setText(RB.getString("DViewCRL.m_jtfNextUpdate.notavailable.text")); m_jtfNextUpdate.setForeground(m_jtfVersion.getForeground()); m_jtfNextUpdate.setEnabled(false); } m_jtfNextUpdate.setCaretPosition(0); // Signature Algorithm m_jtfSignatureAlgorithm.setText(m_crl.getSigAlgName()); m_jtfSignatureAlgorithm.setCaretPosition(0); // Enable/disable extensions button Set<String> critExts = m_crl.getCriticalExtensionOIDs(); Set<String> nonCritExts = m_crl.getNonCriticalExtensionOIDs(); if ((critExts != null && !critExts.isEmpty()) || (nonCritExts != null && !nonCritExts.isEmpty())) { // Extensions m_jbCrlExtensions.setEnabled(true); } else { // No extensions m_jbCrlExtensions.setEnabled(false); } // Populate Revoked Certificates table Set<? extends X509CRLEntry> revokedCertsSet = m_crl.getRevokedCertificates(); if (revokedCertsSet == null) { revokedCertsSet = Collections.emptySet(); } X509CRLEntry[] revokedCerts = revokedCertsSet.toArray(new X509CRLEntry[revokedCertsSet.size()]); RevokedCertsTableModel revokedCertsTableModel = (RevokedCertsTableModel) m_jtRevokedCerts.getModel(); revokedCertsTableModel.load(revokedCerts); // Select first CRL if (revokedCertsTableModel.getRowCount() > 0) { m_jtRevokedCerts.changeSelection(0, 0, false, false); } } /** * CRL entry selected or deselected. Enable/disable the "CRL Extensions" button accordingly (i.e. enable it if only * one extension is selected and it has extensions. */ private void crlEntrySelection() { ListSelectionModel listSelectionModel = m_jtRevokedCerts.getSelectionModel(); if (!listSelectionModel.isSelectionEmpty()) // Entry must be selected { // Only one entry though // TODO: probably no longer necessary? if (listSelectionModel.getMinSelectionIndex() == listSelectionModel.getMaxSelectionIndex()) { // Get serial number of entry int iRow = listSelectionModel.getMinSelectionIndex(); BigInteger serialNumber = (BigInteger) m_jtRevokedCerts.getValueAt(iRow, 0); // Find CRL entry using serial number Set<? extends X509CRLEntry> revokedCertsSet = m_crl.getRevokedCertificates(); X509CRLEntry x509CrlEntry = null; for (X509CRLEntry entry : revokedCertsSet) { if (serialNumber.equals(entry.getSerialNumber())) { x509CrlEntry = entry; break; } } if (x509CrlEntry != null && x509CrlEntry.hasExtensions()) { m_jbCrlEntryExtensions.setEnabled(true); return; } } } // Disable "CRL Extensions" button m_jbCrlEntryExtensions.setEnabled(false); } /** * CRL extensions button pressed or otherwise activated. Show the extensions of the CRL. */ private void crlExtensionsPressed() { DViewExtensions dViewExtensions = new DViewExtensions(this, RB.getString("DViewCRL.Extensions.Title"), true, m_crl); dViewExtensions.setLocationRelativeTo(this); SwingHelper.showAndWait(dViewExtensions); } /** * CRL entry extensions button pressed or otherwise activated. Show the extensions of the selected CRL entry. */ private void crlEntryExtensionsPressed() { ListSelectionModel listSelectionModel = m_jtRevokedCerts.getSelectionModel(); if (!listSelectionModel.isSelectionEmpty()) // Entry must be selected { // Only one entry though // TODO: probably no longer necessary? if (listSelectionModel.getMinSelectionIndex() == listSelectionModel.getMaxSelectionIndex()) { // Get serial number of entry int iRow = listSelectionModel.getMinSelectionIndex(); BigInteger serialNumber = (BigInteger) m_jtRevokedCerts.getValueAt(iRow, 0); // Find CRL entry using serial number Set<? extends X509CRLEntry> revokedCertsSet = m_crl.getRevokedCertificates(); X509CRLEntry x509CrlEntry = null; for (X509CRLEntry entry : revokedCertsSet) { if (serialNumber.equals(entry.getSerialNumber())) { x509CrlEntry = entry; break; } } if (x509CrlEntry != null && x509CrlEntry.hasExtensions()) { DViewExtensions dViewExtensions = new DViewExtensions(this, RB.getString("DViewCRL.EntryExtensions.Title"), true, x509CrlEntry); dViewExtensions.setLocationRelativeTo(this); SwingHelper.showAndWait(dViewExtensions); } } } } }