package com.ca.commons.security;
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.logging.Logger;
import java.util.logging.Level;
import javax.swing.*;
import java.security.*;
import java.security.cert.*;
import java.security.spec.*;
import java.net.URL;
//use Van Bui's Certificate Viewer
import com.ca.commons.cbutil.*;
import com.ca.commons.security.cert.CertViewer;
public class KeystoreGUI extends CBDialog implements ActionListener
{
public boolean standAlone = false;
public static final String ERRORCERT = "<unable to read>";
public static final String DELETEDCERT = "<deleted>";
CBButton viewCert, addCert, deleteCert, passwordButton,
importKeyButton, exportKeyButton;
CBButton[] commandButtons;
protected KeyStore keystore = null;
final JList certList; // final is for ease of use in mouse listener
DefaultListModel certListModel;
public static ImageIcon smallCert;
public static ImageIcon smallKeyCert;
Properties properties;
protected CBHelpSystem helpBroker;
char[] password = null;
protected String keystoreFile;
protected String keystoreType;
private static Logger log = Logger.getLogger(KeystoreGUI.class.getName());
/**
* Whether to cripple the GUI because we're displaying a losing
* key format (e.g. KSE) which doesn't support a bunch of operations...
*/
private boolean crippled = false;
/**
* Whether to additionally cripple the set password because we're
* displaying a key format which doesn't support 'set password'
*/
private boolean cripplePassword = false;
// whether the keystore has been modified and must be written back to disk.
private boolean changed = false;
/**
* This creates the Keystore config window to manage a particular keystore.
* @param owner the parent frame (used for internal GUI stuff)
* @param props the JX property list (used to get and set default keystore directories)
* @param keyStoreLocation the location of the java keystore to manage.
* @param keyStorePassword the password of the encrypted keystore - may be null,
* in which case the user will be prompted.
* @param keyStoreType the java abreviation of the keystore type (typically 'jks' for
* 'java keystore' - the default java file based keystore).
* @param title a meaningfull (to the user) name for the keystore
* @param handlePrivateKeys whether the keystore manager will allow the
* user to associate a private key with a particular certificate.
* @param helpTopic the link into the default java help system (if used). See
* @see com.ca.commons.cbutil.CBHelpSystem
*/
public KeystoreGUI( Frame owner, Properties props, String keyStoreLocation,
char[] keyStorePassword, String keyStoreType, String title,
boolean handlePrivateKeys, String helpTopic, boolean standAlone)
{
super(owner, title, helpTopic); // create modal dialog ...
//if(title.compareToIgnoreCase("SmartKeytool 1.0")==0)
if (standAlone)
{
this.standAlone=true;
// converted this to make it backwardly compatible - CB
try
{
this.owner.setIconImage(getImageIcon("logo_16.gif").getImage());
}
catch (Exception e) {} // we don't care if this stuff up - it's just a nice to have...
}
if ("KSE".equals(keyStoreType))
crippled = true;
properties = props;
password = keyStorePassword;
CertViewer.setProperties(properties);
if (smallCert == null)
{
smallCert = getImageIcon("sslcert.gif");
}
if (smallKeyCert == null)
{
smallKeyCert = getImageIcon("sslkeycert.gif");
}
keystoreFile = keyStoreLocation;
keystoreType = keyStoreType;
display.makeHeavy();
JScrollPane scrollPane = new JScrollPane();
certList = new JList();
/*
* Problem here - some keystores require passwords to
* even look at them, while others don't. Not sure how
* to handle this in general... in the meantime we have a
* a series of hacks...
*/
if (password != null || "JKS".equalsIgnoreCase(keystoreType))
{
setupCertificateList();
}
else if ("KSE".equalsIgnoreCase(keystoreType) && keystoreFile!= null &&
keystoreFile.toLowerCase().endsWith(".der"))
{
setupCertificateList();
cripplePassword = true;
}
else
{
if (setupPasswordAndKeystore(keystoreType, keystoreFile, this)) // no password, = no keystore
{
refreshView(); // reset certListModel
certList.setModel(certListModel); // set the display JList of certs..
}
}
scrollPane.getViewport().setView(certList);
display.add(scrollPane, 1, 1, 2, ((handlePrivateKeys)?7:5));
display.makeLight();
display.add(viewCert = new CBButton(" " + CBIntText.get("View Certificate"), CBIntText.get("View a certificate in detail."), getImageIcon("sslview.gif")), 3, 1);
display.add(addCert = new CBButton(" " + CBIntText.get("Add Certificate"), CBIntText.get("Add a new trusted server certificate"), getImageIcon("ssladd.gif")), 3, 2);
if (crippled)
//addCert.disable();
addCert.setEnabled(false);
display.add(deleteCert = new CBButton(" " + CBIntText.get("Delete Certificate"), CBIntText.get("Delete an unwanted or out of date server certificate"), getImageIcon("ssldelete.gif")), 3, 3);
display.add(passwordButton = new CBButton(" " + CBIntText.get("Set Password"), CBIntText.get("Change the certificate keystore password."), getImageIcon("sslpassword.gif")), 3, 4);
importKeyButton = new CBButton(" " + CBIntText.get("Set Private Key"), CBIntText.get("Match a PKCS-8 private key with a certificate"), getImageIcon("sslprivatekey.gif"));
exportKeyButton = new CBButton(" " + CBIntText.get("Export Private Key"), CBIntText.get("Export the PKCS-8 private key matching a certificate"), getImageIcon("sslexprivatekey.gif"));
if (handlePrivateKeys)
{
display.add(importKeyButton, 3, 5);
display.add(exportKeyButton, 3, 6);
}
commandButtons = new CBButton[] {viewCert, addCert, deleteCert, passwordButton, importKeyButton, exportKeyButton};
for (int i=0; i<commandButtons.length; i++)
{
commandButtons[i].setHorizontalAlignment(SwingConstants.LEFT);
commandButtons[i].addActionListener(this);
}
if (crippled)
{
JButton[] crippledButton = {addCert, deleteCert, exportKeyButton, importKeyButton};
for (int i=0; i<4; i++)
{
//crippledButton[i].disable();
crippledButton[i].setEnabled(false);
crippledButton[i].removeActionListener(this);
crippledButton[i].setToolTipText(CBIntText.get("Not available with this security provider"));
crippledButton[i].setForeground(Color.gray);
}
}
if (cripplePassword)
{
//passwordButton.disable();
passwordButton.setEnabled(false);
passwordButton.removeActionListener(this);
passwordButton.setToolTipText(CBIntText.get("Not available with this security provider"));
passwordButton.setForeground(Color.gray);
}
// special hack for double clicks
MouseListener mouseListener = new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
if (e.getClickCount() == 2)
{
if (e.getModifiers() == MouseEvent.BUTTON1_MASK)
{
//int index = certList.locationToIndex(e.getPoint());
CertItem cert = (CertItem)certList.getSelectedValue();
viewCurrentCert(cert);
}
}
}
};
certList.addMouseListener(mouseListener);
display.add(new JLabel(" "), 3, ((handlePrivateKeys)?7:5)); // padding...
}
/**
* checks actions on the various keystore affecting buttons.
* Note that the OK and Cancel button are handled by doOK() and
* doCancel() inherited from the base class.
*/
public void actionPerformed(ActionEvent e)
{
JButton src = ((JButton)e.getSource());
CertItem cert = (CertItem)certList.getSelectedValue();
if (src == viewCert)
{
viewCurrentCert(cert);
}
else if (src == addCert)
{
addNewCert();
}
else if (src == deleteCert)
{
if(cert==null)
CBUtility.error(CBIntText.get("Please select a certificate to delete."), null);
else
deleteCurrentCert(cert);
}
else if (src == passwordButton)
{
setupPasswords();
}
else if (src == importKeyButton)
{
importKey(cert);
}
else if (src == exportKeyButton)
{
exportKey(cert);
}
}
/**
* If the user is satisfied with their changes, attempt to
* write the keystore. Some checks may be required first,
* depending on the keystore type.
*/
public void doOK()
{
if (changed)
{
/* check that the user has entered a valid passphrase */
if (checkPassword() == false)
return; // nothing to do.
try
{
if (writeKeyStore(password, keystore, keystoreFile, keystoreType) == false)
{
clearPassword(password);
password = null;
return; // error given by writeKeyStore() method.
}
}
catch (Exception e)
{
CBUtility.error(CBIntText.get("Error importing key file."), e);
return;
}
}
changed = false;
// clean up the old password
clearPassword(password);
password = null;
super.doOK();
//System.exit(0); //XXX TEMP
}
public void doCancel()
{
if (changed)
{
String[] options = { CBIntText.get("Revise Changes"), CBIntText.get("Discard Changes") };
int opt = JOptionPane.showOptionDialog(null, CBIntText.get("You have unsaved changes!"), "Warning",
JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
null, options, options[0]);
if (opt == 0) return;
}
super.doCancel();
//System.exit(0); //XXX TEMP
}
/**
* Allows the user to match a private key with a particular certificate.
* (Currently limited to pkcs 8 - other may be possible depending on keystore
* implementation).
* @param certItem the certificate whose private key is to be imported.
*/
protected void importKey(CertItem certItem)
{
try
{
/* Check that the user has selected a certificate to associate with the new key */
if (certItem == null || certItem.getX509Cert() == null)
{
CBUtility.error(CBIntText.get("Please select a certificate to match with a key."), null);
return;
}
/* Get the user to select a pkcs 8 private key file */
File keyFile = getKeyFile(CBIntText.get("Select a pkcs8 private key file"));
if (keyFile == null)
return; // nothing to do.
/* Read the file data into a byte array */
FileInputStream in = new FileInputStream(keyFile);
byte [] buffer = new byte[(int) (keyFile.length())];
in.read(buffer);
in.close();
/* check if this is pem base64 encoded data - if it is, translate it */
if (CBSecurity.isPEM(buffer))
{
//TODO: XXX <your code to handle encrypted private keys here> XXX//
byte[] pemData = CBSecurity.convertFromPEM(buffer, new String(CBSecurity.PEM_KEY_HEADER).getBytes());
if (pemData != null)
buffer = pemData;
else
{
CBUtility.error(CBIntText.get("Unable to load key: does not begin with {0}", new String[] {new String(CBSecurity.PEM_KEY_HEADER)}));
return;
}
}
/* check that the user has entered a valid passphrase */
if (checkPassword() == false)
return; // nothing to do.
/* import key */
String alias = certItem.getAlias();
java.security.cert.Certificate[] certChain = keystore.getCertificateChain(alias);
//XXX <your code to handle unencrypted private keys here> XXX//
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
KeyFactory factory = KeyFactory.getInstance("RSA");
PrivateKey key = factory.generatePrivate(keySpec);
if (certChain == null || certChain.length == 0) // ...which it often does, since cert
{ // chains often aren't stored properly
certChain = new java.security.cert.Certificate[1]; // in the keystore
certChain[0] = certItem.getX509Cert();
}
keystore.setKeyEntry(alias, key, password, certChain);
refreshView();
changed = true;
}
catch (Exception e)
{
CBUtility.error("Error importing key file.", e);
e.printStackTrace();
}
}
/**
* Allows the user to export a private key with a particular certificate.
* (Currently limited to pkcs 8 - other may be possible depending on keystore
* implementation).
* @param certItem the certificate whose private key is to be exported.
*/
protected void exportKey(CertItem certItem)
{
try
{
/* Check that the user has selected a certificate to associate with the new key */
if (certItem == null || certItem.getX509Cert() == null)
{
CBUtility.error(CBIntText.get("Please select a certificate to match with a key."), null);
return;
}
/* Get the user to select a pkcs 8 private key file */
File keyFile = getKeyFile(CBIntText.get("Select a file to save the pkcs8 key to."));
if (keyFile == null)
return; // nothing to do.
/* check that the user has entered a valid passphrase */
if (checkPassword() == false)
return; // nothing to do.
/* read key from keystore */
Key myKey = keystore.getKey(certItem.getAlias(), password);
if (myKey == null)
{
return;
}
byte[] data = myKey.getEncoded();
if (data == null)
{
throw new Exception("Unable to access encoded private key data");
}
if (keyFile.toString().toLowerCase().endsWith(".pem"))
{
data = CBSecurity.convertToPEMPrivateKey(data);
}
FileOutputStream out = new FileOutputStream(keyFile);
out.write(data);
out.close();
}
catch (Exception e)
{
CBUtility.error("Error exporting key file.", e);
e.printStackTrace();
}
}
/**
* This prompts the user to select a pkcs8 file to import, and
* attach to an existing certificate.
* @return the File name of the selected pkcs8 file.
*/
protected File getKeyFile(String title)
{
JFileChooser chooser = new JFileChooser(properties.getProperty("cert.homeDir"));
chooser.addChoosableFileFilter(new CBFileFilter(new String[] {"der", "pem"},"Certificate Files (*.der, *.pem)"));
chooser.setDialogTitle(title);
int option = chooser.showOpenDialog(owner);
while (true)
{
if (option == JFileChooser.APPROVE_OPTION) // only do something if user chose 'ok'
{
File keyFile = chooser.getSelectedFile();
if (keyFile == null)
CBUtility.error(CBIntText.get("Please select a file"));
else
{
properties.setProperty("cert.homeDir", keyFile.getParent());
chooser = null;
return keyFile;
}
}
else
{
chooser = null;
return null; // user selected cancel, or closed the window.
}
}
}
/**
* Uses the CertViewer to display the contents of the selected
* certificate.
* @param cert the certificate to display.
*/
protected void viewCurrentCert(CertItem cert)
{
if (cert == null || cert.getX509Cert() == null) // nothing to do.
{
CBUtility.error(CBIntText.get("Please select a certificate to view."), null);
return;
}
CertViewer viewer = new CertViewer(owner, cert.getX509Cert());
// converted this to make it backwardly compatible - CB
if(standAlone)
{
try
{
this.owner.setIconImage(getImageIcon("logo_16.gif").getImage());
}
catch (Exception e) {} // we don't care if this stuff up - it's just a nice to have...
}
viewer.setVisible(true);
}
/**
* Checks the list to see which the currently selected certificate is,
* and then prompts the user to confirm the deletion.
* @param certItem the certificate to delete.
*/
protected void deleteCurrentCert(CertItem certItem)
{
if (certItem == null)
return; // nothing to do.
int delete = JOptionPane.showConfirmDialog(this, CBIntText.get("delete certificate: {0} ?", new String[] {certItem.getAlias()}),
CBIntText.get("Confirm Certificate Deletion"), JOptionPane.OK_CANCEL_OPTION);
if (delete != JOptionPane.OK_OPTION)
return; // nothing to do
if (keystore == null) // ? Can't see how this would happen
{
CBUtility.error(CBIntText.get("Internal Error: unable to find Certificate Keystore"), null);
return;
}
if (checkPassword() == false)
return; // nothing to do.
try
{
keystore.deleteEntry(certItem.getAlias());
refreshView();
changed = true;
return;
/* DEFER
if (writeKeyStore(password, keystore, keystoreFile) == true)
{
refreshView();
return; // SUCCESS!!!
}
*/
}
catch (KeyStoreException e)
{
CBUtility.error(CBIntText.get("Error - unable to delete key: {0} from key store", new String[] {certItem.getAlias()}), e);
}
// FAILURE!!!
try // try to reset entry in local keystore
{
keystore.setCertificateEntry(certItem.getAlias(), certItem.getX509Cert());
}
catch (Exception e)
{
log.log(Level.WARNING, "unable to recover key store.",e);
}
}
/**
* checks that the user has entered a valid password. If they haven't,
* it prompts for one.
* @return whether a valid password has been entered and checked against
* the keystore.
*/
protected boolean checkPassword()
{
if (password != null)
return true; // we already have a password.
return setupPasswordAndKeystore(keystoreType, keystoreFile, this); // we don't, so try to get one...
}
/**
* <p>This allows the user to enter their password, which remains valid
* for the life of this component. </p>
*
* <p>This also sets up the keystore</p>
* @return whether the password successfully opened the keystore.
*/
public boolean setupPasswordAndKeystore(String keystoreType, String keystoreFile, Component owner)
{
if ((password != null) && (keystore != null)) // no thanks, we already have one...
return true;
String message = CBIntText.get("Enter Key Store Password");
while ((password = getPassword(owner, message)) != null)
{
keystore = readKeyStore(password, keystoreType, keystoreFile);
if (keystore != null)
{
return true; // we have a valid keystore!
}
// this message is only displayed if we go around the loop again.
message = CBIntText.get("Password incorrect. Please try again.");
}
return false; // user hasn't entered a password and has cancelled out.
}
public static char[] getPassword(Component owner, String message)
{
char[] password;
JPasswordField passwordInput = new JPasswordField();
int response = JOptionPane.showConfirmDialog(owner, passwordInput,
message, JOptionPane.OK_CANCEL_OPTION);
if (response != JOptionPane.OK_OPTION)
password = null; // give up, go home
else
password = passwordInput.getPassword();
return password;
}
/**
* Sets up the keystore variable, using the
* current password (may be null) and keystore file.
*/
/*
public static boolean setupKeyStore(char[] password, KeyStore keyStore, String keyStoreType, String keyStoreFile)
{
KeyStore newKeystore = readKeyStore(password, keyStoreType, keyStoreFile);
if (newKeystore == null)
{
return false;
}
else
{
keyStore = newKeystore;
return true;
}
}
*/
/**
* Checks if the given alias name already exists in the
* Keystore.
*/
private boolean listContains(String aliasName)
{
if (aliasName == null) return false;
for (int i=0; i<certListModel.size(); i++)
if (aliasName.equals(((CertItem)certListModel.get(i)).alias))
return true;
return false;
}
/**
* Allows the User to browse to a new Cert (on disk) and
* import it.
*/
protected void addNewCert()
{
CertViewer.CertAndFileName info = CertViewer.loadCertificate(owner);
if (info == null || info.cert == null)
{
return; // no cert selected
}
String alias = null;
if (info.fileName != null)
{
alias = new File(info.fileName).getName();
if (alias != null && alias.indexOf('.')>0)
alias = alias.substring(0, alias.indexOf('.')); // trim to get the stem
}
if (alias == null)
alias = CBIntText.get("default");
boolean nameAlreadyExists = false;
do
{
alias = (String)JOptionPane.showInputDialog(this, CBIntText.get("Please enter a short unique name for this Certificate"),
CBIntText.get("Enter Certificate Alias"), JOptionPane.QUESTION_MESSAGE, null, null, alias);
nameAlreadyExists = listContains(alias);
if (nameAlreadyExists)
{
JOptionPane.showMessageDialog(this, CBIntText.get("That name already exists."),
CBIntText.get("Duplicate Alias"), JOptionPane.ERROR_MESSAGE);
}
}
while (nameAlreadyExists);
if (alias == null || alias.length() == 0)
return; // nothing to do
if (checkPassword() == false)
return; // nothing to do.
try
{
keystore.setCertificateEntry(alias, info.cert);
refreshView();
changed = true;
return;
}
catch (KeyStoreException e)
{
CBUtility.error(CBIntText.get("Error - unable to add key: {0} from key store", new String[] {alias}), e);
}
// FAILURE!
try
{
keystore.deleteEntry(alias); // try to clean up.
}
catch (Exception e)
{}
}
/**
* Reread the key store after an addition or deletion operation,
* and refresh certListModel.
*/
protected void refreshView()
{
CertItem[] certs = getKeyStoreCerts(keystore);
if (certListModel == null)
setupCertificateListGUI();
certListModel.removeAllElements();
for (int i=0; i<certs.length; i++)
certListModel.addElement(certs[i]);
}
/**
* Initialise empty list models, and associate the
* certificate list renderer with the cert list.
*
*/
protected void setupCertificateListGUI()
{
certListModel = new DefaultListModel();
certList.setModel(certListModel);
certList.setCellRenderer(new CertificateListRenderer());
}
/**
* Initialises a selection list of CertItems from the keystore.
*/
protected void setupCertificateList()
{
// Initially read the keystore without a password, for
// simple listing...
keystore = readKeyStore(password, keystoreType, keystoreFile);
setupCertificateListGUI();
if (keystore == null)
JOptionPane.showMessageDialog(this, CBIntText.get("Unable to find/open keystore: {0}", new String[] {keystoreFile}), CBIntText.get("Error: no Keystore"), JOptionPane.ERROR_MESSAGE);
else
refreshView();
}
/**
* The keystore has a particular password protecting its contents.
* This menu allows the user to change that password.
*/
public class PasswordDialog extends CBDialog
{
public JPasswordField old, new1, new2;
public PasswordDialog(Frame owner)
{
super(owner, CBIntText.get("Change the Key Store Password."), null);
addln(new JLabel(getImageIcon("sslpassword.gif")));
addln(new JLabel(CBIntText.get("This screen allows you to enter")));
addln(new JLabel(CBIntText.get("a new key store password")));
addln(new JLabel(" "));
addln(new JLabel(CBIntText.get("Enter the old password")));
addln(old = new JPasswordField());
addln(new JLabel(CBIntText.get("The new Password") + ":"));
addln(new1 = new JPasswordField());
addln(new JLabel(CBIntText.get("Confirm the new Password") + ":"));
addln(new2 = new JPasswordField());
setSize(240, 320);
CBUtility.center(this, owner);
}
}
/**
* This allows the user to change the password used to protect
* the keystore.
*
*/
protected void setupPasswords()
{
PasswordDialog newPassword = new PasswordDialog(owner);
// CB - not sure what's happening here;
//if(standAlone)
// newPassword.setIconImage(new ImageIcon(Theme.getInstance().getDirImages() + "ODlogo.gif")).getImage());
// Various things can go wrong here - keep showing the
// user the password change window until they enter a
// valid set of passwords, or get sick of it...
while (newPassword.wasCancelled() == false)
{
newPassword.setVisible(true);
if (newPassword.wasCancelled())
return; // do nothing.
char[] oldPass, newPass1, newPass2;
oldPass = newPassword.old.getPassword();
newPass1 = newPassword.new1.getPassword();
newPass2 = newPassword.new2.getPassword();
if (Arrays.equals(newPass1, newPass2) == true)
{
// this throws an error directly to the user if it fails
KeyStore newKeystore = readKeyStore(oldPass, keystoreType, keystoreFile);
if (newKeystore != null)
{
if (writeKeyStore(newPass1, newKeystore, keystoreFile, keystoreType) == true)
{
keystore = newKeystore;
password = newPass1;
JOptionPane.showMessageDialog(this, CBIntText.get("Passwords successfully changed!"),
CBIntText.get("Success!"), JOptionPane.INFORMATION_MESSAGE);
return; // SUCCESS!
}
}
else
CBUtility.error(CBIntText.get("Unable to change password - incorrect password entered?"));
}
else
{
CBUtility.error(CBIntText.get("The new passwords were not identical!"), null);
}
}
}
protected void clearPassword(char[] c)
{
if (c != null)
for (int i=0; i<c.length; i++)
c[i] = 0;
}
/**
* This extracts an array of CertItem-s from a keystore,
* for display in the GUI.
* @param keystore the keystore to use.
* @return an array of CertItem-s representing the certificates and aliases
* stored in the keystore.
*/
public static CertItem[] getKeyStoreCerts(KeyStore keystore)
{
try
{
Vector certVector = new Vector(10); // vector of cert items...
//PrivateKey privKey=null;
Enumeration a = keystore.aliases();
while ( a.hasMoreElements() )
{
String alias = (String) a.nextElement();
CertItem item = new CertItem(alias);
if ( keystore.isKeyEntry(alias) )
{
X509Certificate userCert = (X509Certificate)keystore.getCertificate(alias);
item.addX509Cert(userCert);
item.setHasPrivateKey(true);
}
else
{
X509Certificate userCert = (X509Certificate)keystore.getCertificate(alias);
item.addX509Cert(userCert);
}
certVector.add(item);
}
return (CertItem[]) certVector.toArray(new CertItem[0]);
}
catch (Exception e)
{
CBUtility.error(CBIntText.get("Error reading certificate from keystore."), e);
return null;
}
}
/**
* initialises the keystore by reading the saved keystore file.
* @param pass the password protecting the keystore. If this is
* null, the keystore will be read-only, and no validation
* will be performed.
* @param storeType - the type of the keystore. Unless a custom
* security provider is being used, this will almost certainly
* be 'jks'.
* @param keyFile the file name of the keystore.
* @return the new keystore, or null if an error occurred.
*/
public static KeyStore readKeyStore(char[] pass, String storeType, String keyFile)
{
//byte[] b=null;
try
{
KeyStore keystore = KeyStore.getInstance( storeType ); // storeType is usually 'jks' for default java keystore
FileInputStream fis = new FileInputStream(keyFile);
keystore.load(fis, pass);
fis.close();
return keystore;
}
catch (Exception e)
{
CBUtility.error(CBIntText.get("Error opening certificate keystore {0}. Probably an incorrect password", new String[] {keyFile}), e);
return null;
}
}
/**
* writes the keystore to a password protected file.
* @param password the password to use while saving it.
* @param keystore the certificate key store to save.
* @param keyFile the name of the file to save to.
* @param keystoreType the type of store - e.g. "JKS" or "KSE" or "PKCS12"
* @return the success status of the operation.
*/
public static boolean writeKeyStore(char[] password, KeyStore keystore, String keyFile, String keystoreType)
{
if ("KSE".equalsIgnoreCase(keystoreType))
{
CertItem[] certs = getKeyStoreCerts(keystore);
if (certs.length > 2)
return givePKCS12ErrorMsg(CBIntText.get("This PKCS12 File can only have one certificate, one key, and one CA certificate"));
if (certs.length == 2 && certs[0].hasPrivateKey && certs[1].hasPrivateKey)
return givePKCS12ErrorMsg(CBIntText.get("This PKCS12 File can only have one certificate, one key, and one CA certificate"));
// XXXcheck for if second cert if server certificate?
}
FileOutputStream fos = null;
try
{
if (password == null)
throw new KeyStoreException("null password not allowed");
fos = new FileOutputStream(keyFile);
keystore.store(fos, password);
fos.close();
return true;
}
catch (Exception e) // IOException or KeyStoreException
{
CBUtility.error(CBIntText.get("Error saving certificate keystore.") +
"\n" + CBIntText.get("Probably an invalid password"), e);
// try to clean up any mess.
if (fos != null)
try {fos.close();} catch(IOException e2) {}
return false;
}
}
/**
* Utility to reduce code duplication above
*/
private static boolean givePKCS12ErrorMsg(String msg)
{
CBUtility.error(msg);
return false;
}
/**
* A representation of a certificate that is displayed
* in the certificate list.
*/
public static class CertItem
{
public String alias;
public X509Certificate x509Cert = null;
public boolean hasPrivateKey = false;
/**
* Initialises a certitem with the alias name of a
* certificate only (the actual cert can be added
* seperately)
* @param certAlias the alias of the certificate, the arbitrary user assigned
* name the certificate is labelled by in the keystore.
*/
public CertItem(String certAlias)
{
this(certAlias, null);
}
/**
* Initialises a certItem with the alias name of a
* certificate and the actual certificate data.
* @param certAlias the alias of the certificate, the arbitrary user assigned
* name the certificate is labelled by in the keystore.
* @param cert the actual X509 Certificate data.
*/
public CertItem(String certAlias, X509Certificate cert)
{
alias = certAlias;
x509Cert = cert;
}
/**
* Adds (or Replaces) the X509Cert data.
* @param x the actual X509 Certificate data.
*/
public void addX509Cert(X509Certificate x)
{
x509Cert = x;
}
/**
* Returns a formatted string identifying the cert by the alias.
* @return the alias assigned to this cert.
*/
public String toString()
{
if (hasPrivateKey)
return "<html><b><font color=black>" + alias + "</font><br><font color=blue>(has private key)</font></b></html>";
else
return alias;
}
/**
* Returns a formatted string identifying the cert by the alias.
* @return the alias assigned to this cert.
*/
public String getSelectedText()
{
if (hasPrivateKey)
return "<html><b><font color=white>" + alias + "</font><br><font color=white>(has private key)</font></b></html>";
else
return alias;
}
/**
* returns the raw alias for this cert.
* @return the alias assigned to this cert.
*/
public String getAlias()
{
return alias;
}
/**
* Returns an image representing this CertItem.
*/
public ImageIcon getIcon()
{
if (hasPrivateKey)
return smallKeyCert;
else
return smallCert;
}
/**
* Returns the X509 certificate data (may be null if this hasn't
* been set).
* @return the X509 certificate stored in this CertItem
*/
public X509Certificate getX509Cert()
{
return x509Cert;
}
public void setHasPrivateKey(boolean state)
{
hasPrivateKey = state;
}
public boolean getHasPrivateKey()
{
return hasPrivateKey;
}
}
/**
* A quicky cell renderer to allow us to display active and pending
* queries in different colours, and to display the text of a
* QueryBroker object.
*/
class CertificateListRenderer extends JLabel implements ListCellRenderer
{
Color highlight = new Color(0,0,128); // Colour of 'selected' text
CertificateListRenderer()
{
setOpaque(true);
}
public Component getListCellRendererComponent(JList list, Object value, int index,
boolean isSelected, boolean cellHasFocus)
{
if (value instanceof CertItem == false) // should never happen :-)
{
System.err.println("Rendering error in KeystoreGUI");
setText(ERRORCERT);
return this;
}
if (index == -1)
{
index = list.getSelectedIndex();
if (index == -1)
{
setText("<error>");
return this;
}
}
if (value == null) // shouldn't happen
{
setBackground(Color.white);
setForeground(Color.gray);
setText("<deleted>");
return this;
}
CertItem item = (CertItem)value;
setIcon(item.getIcon());
if (isSelected)
{
setText(item.getSelectedText());
setBackground(highlight);
setForeground(Color.white);
}
else
{
setText(item.toString());
setBackground(Color.white);
setForeground(Color.black);
}
return this;
}
}
public ImageIcon getImageIcon(String name)
{
try
{
String path = Theme.getInstance().getDirImages() + name;
File imageFile = new File(path);
if (imageFile.exists())
{
ImageIcon newIcon = new ImageIcon(path);
return newIcon;
}
}
catch (Exception e) {} // ignore; try load via class loader mechanism
System.out.println("debug: trying to load jar image " + name + " from: " + this.getClass().getResource(name));
try
{
//TODO: Check this out for the Theme-izing
return new ImageIcon(this.getClass().getResource("/" + name));
}
catch (Exception e)
{
System.out.println("Error loading images; " + name + " not found: " + e.getMessage());
}
return null;
}
private static void printUsageAndExit()
{
System.out.println("USAGE: java KeystoreGUI [keystore file|path] [keystore password] [keystore type] [provider]\n" +
"(defaults are 'security/clientcerts' and 'jks'");
System.exit(0);
}
/**
* Main method for stand alone usage and provider testing.
*/
public static void main(String[] argsv)
{
String keystoreType = "jks";
String provider = null;
String password = null;
Frame rootFrame = new Frame();
CBUtility.initDefaultDisplay(rootFrame);
// stand alone demo...
System.out.println("running KeystoreGUI 1.0 stand alone - Chris Betts 2002 / Santthosh Babu Selvadurai 2007\n");
String localDir = System.getProperty("user.dir") + File.separator;
Properties props = new Properties();
/*
* This sets the directory that the file browsers start in.
* This can be saved/read from file to allow the user to start
* loading/saving from the same place.
*/
props.setProperty("cert.homeDir", localDir + "certs" + File.separator);
/*
* This simply sets the directory where the GUI will try to load
* its button images from.
*/
props.setProperty("dir.images", Theme.getInstance().getDirImages());
/*
* Set the location of the java keystore to manipulate.
*/
String keystoreName = localDir + "security" + File.separator + "clientcerts";
/*-SmartKeytool Utility- Commented for graphical packaging
if (argsv.length < 1)
printUsageAndExit();
if (argsv[0].startsWith("-h"))
printUsageAndExit();
if (argsv[0].length() < 2)
{
keystoreName = argsv[0];
}
else if (argsv[0].charAt(1) == ':' || argsv[0].charAt(0) == '/') // absolute path
{
keystoreName = argsv[0];
}
else // assume relative path.
{
keystoreName = localDir + argsv[0];
}
/*
* Keystore Password
if (argsv.length > 1)
{
password = argsv[1];
}
/*
* Read optional keystore type (e.g. "KSE")
if (argsv.length > 2)
{
keystoreType = argsv[2];
}
/*
* Read optional provider (e.g. com.ca.pki.security.provider.KeyStoreEngine )
*-SmartKeytool Utility- Commented for graphical packaging*/
/*-SmartKeytool Utility- Commented for graphical packaging
*
* Standalone repackaging done by Santthosh Babu Selvadurai
* sbselvad@ncsu.edu/santthosh@ieee.org
*
*/
javax.swing.UIManager ui = new javax.swing.UIManager();
try
{
ui.setLookAndFeel(ui.getSystemLookAndFeelClassName());
JFrame tempFrame = new JFrame();
KeystorePrompt kp = new KeystorePrompt(tempFrame);
kp.setSize(300,200);
keystoreName = kp.getKeystorePath();
password = kp.getKeystorePassword();
}
catch(Exception e)
{
e.printStackTrace();
System.exit(0);
}
if (argsv.length > 3)
{
provider = argsv[3];
// register the new provider
try
{
Class providerClass = Class.forName(provider);
Provider providerObject = (Provider)providerClass.newInstance();
Security.insertProviderAt(providerObject, 1);
System.out.println("\nPROVIDER: " + providerObject.getName() + " v" + providerObject.getVersion() + " has been registered ");
}
catch (Exception e)
{
System.err.println("\n*** unable to load new security provider: " + ((provider==null)?"null":provider));
System.err.println(e + "\n");
printUsageAndExit();
}
}
Provider[] current = Security.getProviders();
for (int i=0; i<current.length; i++)
System.out.println("registered security providers: " + i + " = " + current[i].getName() + " " + current[i].getInfo());
// Extend KeystoreGUI to add 'exit on close' behaviour
class StandaloneKeystore extends KeystoreGUI
{
public StandaloneKeystore( Frame owner, Properties props, String keyStoreLocation, char[] pwd, String keystoreType, String title, boolean handlePrivateKeys, String helpTopic)
{
super(owner, props, keyStoreLocation, pwd, keystoreType, title, handlePrivateKeys, helpTopic, true);
this.setResizable(false);
this.pack();
}
public void doOK()
{
super.doOK();
System.exit(0);
}
public void doCancel()
{
super.doCancel();
System.exit(0);
}
}
char[] pwd = null; //((password==null)?null:password.toCharArray());
StandaloneKeystore gui = new StandaloneKeystore(rootFrame, props, keystoreName, pwd, keystoreType, "SmartKeytool 1.0", true, null);
gui.setSize(450,440);
CBUtility.center(gui, null);
gui.setVisible(true);
}
}