Skip to content

Commit

Permalink
Add export selected certificates (#490)
Browse files Browse the repository at this point in the history
* add export selected certificates

* remove checkbox order, add icon, case 'select a key pair entry'

* remove tabs

* fix NPE examine crt from url

* fix NPE verify from view certificate
  • Loading branch information
jgrateron authored Apr 14, 2024
1 parent 8369071 commit 2d88260
Show file tree
Hide file tree
Showing 9 changed files with 260 additions and 18 deletions.
73 changes: 73 additions & 0 deletions kse/src/main/java/org/kse/crypto/x509/X509CertUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,79 @@ public static X509Certificate[] orderX509CertChain(X509Certificate[] certs) {
// Return longest path
return longestPath.toArray(new X509Certificate[0]);
}
/*
* Tries to sort the certificates according to their hierarchy,
* and adds at the end those that have no dependencies.
*/
public static X509Certificate[] orderX509CertsChain(X509Certificate[] certs) {
if (certs == null) {
return new X509Certificate[0];
}
if (certs.length <= 1) {
return certs;
}
ArrayList<ArrayList<X509Certificate>> paths = new ArrayList<>();
for (X509Certificate cert : certs) {
ArrayList<X509Certificate> path = new ArrayList<>();
path.add(cert);
for (X509Certificate issuerCert : certs) {
if (certificatesEquals(issuerCert, cert)) {
continue;
}
if (isIssuedBy(cert, issuerCert)) {
path.add(issuerCert);
}
}
if (path.size() > 1) {
paths.add(path);
}
}
List<X509Certificate> listCertificates = new ArrayList<>();
for (ArrayList<X509Certificate> path : paths) {
X509Certificate cert = path.get(0);
X509Certificate issuerCert = path.get(1);
int posIssuer = -1;
int posCert = -1;
for (int i = 0; i < listCertificates.size(); i++) {
X509Certificate cert2 = listCertificates.get(i);
if (certificatesEquals(issuerCert, cert2)) {
posIssuer = i;
}
if (certificatesEquals(cert, cert2)) {
posCert = i;
}
}
if (posIssuer == -1) {
if (posCert == -1) {
listCertificates.add(cert);
}
listCertificates.add(issuerCert);
} else {
listCertificates.add(posIssuer, cert);
}
}
if (listCertificates.size() != certs.length) {
for (X509Certificate cert1 : certs) {
boolean found = false;
for (X509Certificate cert2 : listCertificates) {
if (certificatesEquals(cert1, cert2)) {
found = true;
break;
}
}
if (!found) {
listCertificates.add(cert1);
}
}
}
return listCertificates.toArray(new X509Certificate[0]);
}

private static boolean certificatesEquals(X509Certificate cert1, X509Certificate cert2) {
return cert1.getSubjectX500Principal().equals(cert2.getSubjectX500Principal())
&& cert1.getIssuerX500Principal().equals(cert2.getIssuerX500Principal())
&& cert1.getSerialNumber().equals(cert2.getSerialNumber());
}

private static X509Certificate findIssuedCert(X509Certificate issuerCert, X509Certificate[] certs) {
// Find a certificate issued by the supplied certificate
Expand Down
9 changes: 9 additions & 0 deletions kse/src/main/java/org/kse/gui/KseFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
import org.kse.gui.actions.ExportKeyPairCertificateChainAction;
import org.kse.gui.actions.ExportKeyPairPrivateKeyAction;
import org.kse.gui.actions.ExportKeyPairPublicKeyAction;
import org.kse.gui.actions.ExportSelectedCertificatesAction;
import org.kse.gui.actions.ExportTrustedCertificateAction;
import org.kse.gui.actions.ExportTrustedCertificatePublicKeyAction;
import org.kse.gui.actions.FindAction;
Expand Down Expand Up @@ -400,6 +401,7 @@ public final class KseFrame implements StatusBar {
private JMenuItem jmiMultEntrySelCopy;
private JMenuItem jmiMultiEntrySelDelete;
private JMenuItem jmiMultiEntryCompare;
private JMenuItem jmiMultiEntryExport;

//
// Main display controls
Expand Down Expand Up @@ -530,6 +532,7 @@ public final class KseFrame implements StatusBar {
private final SetKeyPasswordAction setKeyPasswordAction = new SetKeyPasswordAction(this);
private final DeleteKeyAction deleteKeyAction = new DeleteKeyAction(this);
private final RenameKeyAction renameKeyAction = new RenameKeyAction(this);
private final ExportSelectedCertificatesAction exportSelectedCertificatesAction = new ExportSelectedCertificatesAction(this);

//
// Action map keys - map input to action
Expand Down Expand Up @@ -2197,11 +2200,17 @@ private void initKeyStoreEntryPopupMenus() {
new StatusBarChangeHandler(jmiMultiEntryCompare,
(String) compareCertificateAction.getValue(Action.LONG_DESCRIPTION), this);

jmiMultiEntryExport = new JMenuItem(exportSelectedCertificatesAction);
jmiMultiEntryExport.setToolTipText(null);
new StatusBarChangeHandler(jmiMultiEntryExport,
(String) exportSelectedCertificatesAction.getValue(Action.LONG_DESCRIPTION), this);

jpmMultiEntrySel = new JPopupMenu();
jpmMultiEntrySel.add(jmiMultiEntrySelCut);
jpmMultiEntrySel.add(jmiMultEntrySelCopy);
jpmMultiEntrySel.add(jmiMultiEntrySelDelete);
jpmMultiEntrySel.add(jmiMultiEntryCompare);
jpmMultiEntrySel.add(jmiMultiEntryExport);

}

Expand Down
20 changes: 10 additions & 10 deletions kse/src/main/java/org/kse/gui/actions/ExamineClipboardAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -240,19 +240,19 @@ private void downloadCrl(URL url) throws IOException, CryptoException {
}

private void downloadCert(URL url) throws IOException, CryptoException {
HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
int status = urlConn.getResponseCode();
if (isRedirect(status)) {
String newUrl = urlConn.getHeaderField("Location");
url = new URL(newUrl);
urlConn = (HttpURLConnection) url.openConnection();
}
HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
int status = urlConn.getResponseCode();
if (isRedirect(status)) {
String newUrl = urlConn.getHeaderField("Location");
url = new URL(newUrl);
urlConn = (HttpURLConnection) url.openConnection();
}
try (InputStream is = urlConn.getInputStream()) {
X509Certificate[] certs = X509CertUtil.loadCertificates(IOUtils.toByteArray(is));
if (certs != null && certs.length > 0) {
DViewCertificate dViewCertificate = new DViewCertificate(frame, MessageFormat.format(
resExt.getString("DViewExtensions.ViewCert.Title"), url.toString()), certs, null,
DViewCertificate.NONE);
DViewCertificate dViewCertificate = new DViewCertificate(frame,
MessageFormat.format(resExt.getString("DViewExtensions.ViewCert.Title"), url.toString()), certs,
this.kseFrame, DViewCertificate.NONE);
dViewCertificate.setLocationRelativeTo(frame);
dViewCertificate.setVisible(true);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package org.kse.gui.actions;

import java.awt.Toolkit;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.ImageIcon;
import javax.swing.JOptionPane;

import org.kse.crypto.keystore.KeyStoreUtil;
import org.kse.crypto.x509.X509CertUtil;
import org.kse.gui.KseFrame;
import org.kse.gui.dialogs.importexport.DExportCertificates;
import org.kse.gui.error.DError;
import org.kse.utilities.history.KeyStoreHistory;
import org.kse.utilities.history.KeyStoreState;
import org.kse.utilities.io.FileNameUtil;

/**
* Action to export selected certificates.
*/
public class ExportSelectedCertificatesAction extends KeyStoreExplorerAction {

private static final long serialVersionUID = 1L;

public ExportSelectedCertificatesAction(KseFrame kseFrame) {
super(kseFrame);
putValue(LONG_DESCRIPTION, res.getString("ExportSelectedCertificatesAction.statusbar"));
putValue(NAME, res.getString("ExportSelectedCertificatesAction.text"));
putValue(SHORT_DESCRIPTION, res.getString("ExportSelectedCertificatesAction.tooltip"));
putValue(SMALL_ICON, new ImageIcon(
Toolkit.getDefaultToolkit().createImage(getClass().getResource("images/exportselectedcerts.png"))));
}

@Override
protected void doAction() {
List<X509Certificate> listCertificate = getCertificates();
File exportFile = null;
try {
if (listCertificate.size() > 0) {
KeyStoreHistory history = kseFrame.getActiveKeyStoreHistory();
String fileName = FileNameUtil.removeExtension(history.getName());
DExportCertificates dExportCertificates = new DExportCertificates(frame, fileName, false, true);
dExportCertificates.setLocationRelativeTo(frame);
dExportCertificates.setVisible(true);

if (!dExportCertificates.exportSelected()) {
return;
}
X509Certificate[] certs = listCertificate.toArray(new X509Certificate[0]);
certs = X509CertUtil.orderX509CertsChain(certs);
exportFile = dExportCertificates.getExportFile();

boolean pemEncode = dExportCertificates.pemEncode();

byte[] encoded = null;

if (dExportCertificates.exportFormatX509()) {
encoded = X509CertUtil.getCertsEncodedX509Pem(certs).getBytes();
} else if (dExportCertificates.exportFormatPkcs7()) {
if (pemEncode) {
encoded = X509CertUtil.getCertsEncodedPkcs7Pem(certs).getBytes();
} else {
encoded = X509CertUtil.getCertsEncodedPkcs7(certs);
}
} else if (dExportCertificates.exportFormatPkiPath()) {
encoded = X509CertUtil.getCertsEncodedPkiPath(certs);
} else if (dExportCertificates.exportFormatSpc()) {
encoded = X509CertUtil.getCertsEncodedPkcs7(certs); // SPC is just DER PKCS #7
}
exportEncodedCertificate(encoded, exportFile);
JOptionPane.showMessageDialog(frame,
res.getString("ExportSelectedCertificatesAction.ExportCertificateSuccessful.message"),
res.getString("ExportSelectedCertificatesAction.ExportCertificate.Title"),
JOptionPane.INFORMATION_MESSAGE);
} else {
JOptionPane.showMessageDialog(frame, res.getString("ExportSelectedCertificatesAction.onemore.message"),
res.getString("ExportSelectedCertificatesAction.ExportCertificate.Title"),
JOptionPane.WARNING_MESSAGE);
}
} catch (FileNotFoundException ex) {
String message = MessageFormat.format(res.getString("ExportSelectedCertificatesAction.NoWriteFile.message"),
exportFile);

JOptionPane.showMessageDialog(frame, message,
res.getString("ExportSelectedCertificatesAction.ExportCertificate.Title"),
JOptionPane.WARNING_MESSAGE);
} catch (Exception ex) {
DError.displayError(frame, ex);
}
}

private List<X509Certificate> getCertificates() {
KeyStoreHistory history = kseFrame.getActiveKeyStoreHistory();
KeyStoreState currentState = history.getCurrentState();

String[] aliases = kseFrame.getSelectedEntryAliases();
try {
List<X509Certificate> listCertificates = new ArrayList<>();
KeyStore keyStore = currentState.getKeyStore();
for (String alias : aliases) {
if (KeyStoreUtil.isTrustedCertificateEntry(alias, keyStore)) {
Certificate certificate = keyStore.getCertificate(alias);
listCertificates.add((X509Certificate) certificate);
} else if (KeyStoreUtil.isKeyPairEntry(alias, keyStore)) {
Certificate[] chain = keyStore.getCertificateChain(alias);
if (chain != null) {
for (Certificate certificate : chain) {
listCertificates.add((X509Certificate) certificate);
}
}
}
}
return listCertificates;
} catch (Exception ex) {
DError.displayError(frame, ex);
return Collections.emptyList();
}
}

private void exportEncodedCertificate(byte[] encoded, File exportFile) throws IOException {
try (FileOutputStream fos = new FileOutputStream(exportFile)) {
fos.write(encoded);
fos.flush();
}
}
}
3 changes: 2 additions & 1 deletion kse/src/main/java/org/kse/gui/dialogs/DViewCertificate.java
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ private void initComponents(X509Certificate[] certs) throws CryptoException {

jbVerify = new JButton(res.getString("DViewCertificate.jbVerify.text"));
jbVerify.setToolTipText(res.getString("DViewCertificate.jbVerify.tooltip"));
jbVerify.setVisible(importExport != NONE);
PlatformUtil.setMnemonic(jbVerify, res.getString("DViewCertificate.jbVerify.mnemonic").charAt(0));

Container pane = getContentPane();
Expand Down Expand Up @@ -302,7 +303,7 @@ private void initComponents(X509Certificate[] certs) throws CryptoException {
pane.add(jbExport, "hidemode 1");
pane.add(jbExtensions, "");
pane.add(jbPem, "");
pane.add(jbVerify, "");
pane.add(jbVerify, "hidemode 1");
pane.add(jbAsn1, "wrap");
pane.add(new JSeparator(), "spanx, growx, wrap 15:push");
pane.add(jbOK, "spanx, tag ok");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ public class DExportCertificates extends JEscDialog {
private boolean formatPkiPath;
private boolean formatSpc;
private boolean pemEncode;
private boolean certificateSelected = false;

/**
* Creates a new DExportCertificate dialog.
Expand All @@ -106,6 +107,14 @@ public DExportCertificates(JFrame parent, String certificateAlias, boolean chain
initComponents();
}

public DExportCertificates(JFrame parent, String certificateAlias, boolean chain, boolean certificateSelected) {
super(parent, Dialog.ModalityType.DOCUMENT_MODAL);
this.certificateAlias = certificateAlias;
this.chain = chain;
this.certificateSelected = certificateSelected;
initComponents();
}

private void initComponents() {
jlExportLength = new JLabel(res.getString("DExportCertificates.jlExportLength.text"));

Expand Down Expand Up @@ -155,7 +164,9 @@ private void initComponents() {
jcbExportPem = new JCheckBox();
jcbExportPem.setSelected(true);
jcbExportPem.setToolTipText(res.getString("DExportCertificates.jcbExportPem.tooltip"));

if (jrbExportChain.isSelected() && jrbExportX509.isSelected()) {
jcbExportPem.setEnabled(false);
}
jlExportFile = new JLabel(res.getString("DExportCertificates.jlExportFile.text"));

jtfExportFile = new JTextField(30);
Expand Down Expand Up @@ -282,12 +293,15 @@ public void windowClosing(WindowEvent evt) {
}
});

if (chain) {
setTitle(MessageFormat.format(res.getString("DExportCertificates.CertificateChain.Title"),
certificateAlias));
} else {
setTitle(MessageFormat.format(res.getString("DExportCertificates.Certificate.Title"), certificateAlias));
}
if (certificateSelected) {
setTitle(MessageFormat.format(res.getString("DExportCertificates.CertificateSelected.Title"),
certificateAlias));
} else if (chain) {
setTitle(MessageFormat.format(res.getString("DExportCertificates.CertificateChain.Title"),
certificateAlias));
} else {
setTitle(MessageFormat.format(res.getString("DExportCertificates.Certificate.Title"), certificateAlias));
}

setResizable(false);

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,14 @@ ExportTrustedCertificateAction.statusbar = Export the
ExportTrustedCertificateAction.text = Export Certificate
ExportTrustedCertificateAction.tooltip = Export Trusted Certificate entry

ExportSelectedCertificatesAction.statusbar = Export the Selected Certificates entries as X.509 PKCS #7, PKI Path or SPC
ExportSelectedCertificatesAction.text = Export Certificates
ExportSelectedCertificatesAction.tooltip = Export Selected Certificates
ExportSelectedCertificatesAction.ExportCertificate.Title = Export Selected Certificates
ExportSelectedCertificatesAction.ExportCertificateSuccessful.message = Export Selected Certificates Successful.
ExportSelectedCertificatesAction.onemore.message = You must select one or more certificates
ExportSelectedCertificatesAction.NoWriteFile.message = Could not write to file ''{0}''.

ExportTrustedCertificatePublicKeyAction.ExportPublicKeyOpenSsl.Title = Export Public Key as OpenSSL
ExportTrustedCertificatePublicKeyAction.ExportPublicKeyOpenSslSuccessful.message = Export Public Key as OpenSSL Successful.
ExportTrustedCertificatePublicKeyAction.NoAccessEntry.message = Could not access KeyStore entry ''{0}''.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

DExportCertificates.Certificate.Simple.Title = Export Certificate
DExportCertificates.Certificate.Title = Export Certificate ''{0}''
DExportCertificates.CertificateSelected.Title = Export Selected Certificates ''{0}''
DExportCertificates.CertificateChain.Simple.Title = Export Certificate Chain
DExportCertificates.CertificateChain.Title = Export Certificate Chain ''{0}''
DExportCertificates.ChooseExportFile.Title = Choose Certificate Export File
Expand Down

0 comments on commit 2d88260

Please sign in to comment.