我们使用java实现SSL安全连接,在遇到未信任的证书时,弹出一个对话框,让用户选择是否信任该站点,信任的话,就把此站点提供的证书导入本地证书库。
为了实现此功能,我们需要重载X509TrustManager类
StoreCertTrustManager.java
----------------------------------begin--------------------------------------------------
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.security.*;
import java.security.cert.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import javax.net.ssl.*;
/**
* This class implements a TrustManager for authenticating the servers certificate.
* It enhances the default behaviour.
*/
class StoreCertTrustManager implements X509TrustManager {
/** The trustmanager instance used to delegate to default behaviour.*/
private TrustManager tm=null;
/** Password for own keystore */
private final char[] keyStorePassword=new String("changeit").toCharArray();
/** Path to own keystore. Store it into the home directory to avoid permission problems.*/
private final String keyStorePath=System.getProperty("user.home")+"/https-keystore";
/** The stream for reading from the keystore. */
FileInputStream keyStoreIStream=null;
/** The instance of the keystore */
private KeyStore keyStore=null;
/**
* Creates a TrustManager which first checks the default behaviour of the X509TrustManager.
* If the default behaviour throws a CertificateException ask the user if the certificate
* should be declared trustable.
*
* @throws Exception: If SSL - initialization failed.
*/
StoreCertTrustManager() throws Exception {
/* Try to set the truststore system property to our keystore
* if we have the appropriate permissions.*/
try{
File httpsKeyStore=new File(keyStorePath);
if(httpsKeyStore.exists()==true) {
System.setProperty("javax.net.ssl.trustStore",keyStorePath);
}
}catch(SecurityException se) {}
/* Create the TrustManagerFactory. We use the SunJSSE provider
* for this purpose.*/
TrustManagerFactory tmf=TrustManagerFactory.getInstance("SunX509", "SunJSSE");
tmf.init((java.security.KeyStore)null);
tm=tmf.getTrustManagers()[0];
/* Something failed we could not get a TrustManager instance.*/
if(tm == null) {
throw new SSLException("Could not get default TrustManager instance.");
}
/* Create the file input stream for the own keystore. */
try{
keyStoreIStream = new FileInputStream(keyStorePath);
} catch( FileNotFoundException fne ) {
// If the path does not exist then a null stream means
// the keystore is initialized empty. If an untrusted
// certificate chain is trusted by the user, then it will be
// saved in the file pointed to by keyStorePath.
keyStoreIStream = null;
}
/* Now create the keystore. */
try{
keyStore=KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(keyStoreIStream,keyStorePassword);
}catch(KeyStoreException ke) {
System.out.println("Loading of https keystore from file <"+keyStorePath+"> failed. error message: "+ke.getMessage());
keyStore=null;
}
}
/**
* Authenticates a client certificate. For we don't need that case only implement the
* default behaviour.
*
* @param chain In: The certificate chain to be authenticated.
* @param authType In: The key exchange algorithm.
*/
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
((X509TrustManager)tm).checkClientTrusted(chain,authType);
}
/**
* Authenticates a server certificate. If the given certificate is untrusted ask the
* user whether to proceed or not.
*
* @param chain In: The certificate chain to be authenticated.
* @param authType In: The key exchange algorithm.
*/
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
/* Output the certifcate chain for debugging purposes */
System.out.println("got X509 certificate from server:");
for(int i=0; i<chain.length; i++) {
System.out.println("chain["+i+"]: "+chain[i].getIssuerDN().getName());
}
try{
/* First try the default behaviour. */
((X509TrustManager)tm).checkServerTrusted(chain,authType);
}catch(CertificateException ce) {
System.out.println("in checkServerTrusted: authType: "+authType+", got certificate exception: "+ce.getMessage());
/* If we got here the certificate is untrusted. */
/* If we could not craete a keystore instance forward the certificate exception. So we have
* at least the default behaviour. */
if(keyStore==null || chain == null || chain.length==0) {
throw(ce);
}
try{
/* If we could not find the certificate in the keystore
* ask the user if it should be treated trustable. */
AskForTrustability ask=new AskForTrustability (chain);
boolean trustCert=ask.showCertificateAndGetDecision();
if(trustCert==true) {
// Add Chain to the keyStore.
for (int i = 0; i < chain.length; i++){
keyStore.setCertificateEntry(chain[i].getIssuerDN().toString(), chain[i]);
}
// Save keystore to file.
FileOutputStream keyStoreOStream = new FileOutputStream(keyStorePath);
keyStore.store(keyStoreOStream, keyStorePassword);
keyStoreOStream.close();
keyStoreOStream = null;
System.out.println("Keystore saved in " + keyStorePath);
} else {
throw(ce);
}
}catch(Exception ge) {
/* Got an unexpected exception so throw the original exception. */
System.out.println("in checkServerTrusted: got exception type: "+ge.getClass()+" message: "+ge.getMessage());
throw ce;
}
}
}
/**
* Merges the system wide accepted issuers and the own ones and
* returns them.
*
* @return: Array of X509 certificates of the accepted issuers.
*/
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
X509Certificate[] cf=((X509TrustManager)tm).getAcceptedIssuers();
X509Certificate[] allCfs=cf;
if(keyStore != null) {
try{
Enumeration ownCerts=keyStore.aliases();
Vector certsVect=new Vector();
while(ownCerts.hasMoreElements()) {
Object cert=ownCerts.nextElement();
certsVect.add(keyStore.getCertificate(cert.toString()));
}
int newLength=cf.length+certsVect.size();
allCfs=new X509Certificate[newLength];
Iterator it=certsVect.iterator();
for(int i=0; i<newLength ; i++) {
if(i<cf.length){
allCfs=cf;
}
else {
allCfs=(X509Certificate[])it.next();
}
}
}catch(KeyStoreException e) {}
}
for(int i=0; i<allCfs.length;i++) {
System.out.println("allCfs["+i+"]: "+allCfs[i].getIssuerDN());
}
return allCfs;
}
/**
* This class implements an interactive dialog. It shows the contents of a
* certificate and asks the user if it is trustable or not.
*/
class AskForTrustability implements ActionListener, ListSelectionListener {
private JButton yes=new JButton("Yes"),no=new JButton("No");
/** default to not trustable */
private boolean isTrusted=false;
private JDialog trust=null;
private JList certItems=null;
private JTextArea certValues=null;
private JComboBox certChain=null;
private final String certParms[]={"Version","Serial Number","Signature Algorithm", "Issuer", "Validity Period", "Subject", "Signature","Certificate Fingerprint"};
private X509Certificate[] chain;
private int chainIdx=0;
/**
* Creates an instance of the class and stores the certificate to show internally.
*
* @param chain In: The certificate chain to show.
*/
AskForTrustability (X509Certificate[] chain) {
this.chain=chain;
}
/**
* This method shows a dialog with all interesting information of the certificate and
* asks the user if the certificate is trustable or not. This method blocks until
* the user presses the 'Yes' or 'No' button.
*
* @return: true: The certificate chain is trustable
* false: The certificate chain is not trustable
*/
public boolean showCertificateAndGetDecision() {
if(chain == null || chain.length == 0) {
return false;
}
trust=new JDialog((Frame)null,"Untrusted server certificate for SSL connection",true);
Container cont=trust.getContentPane();
GridBagLayout gl=new GridBagLayout();
cont.setLayout(gl);
JPanel pLabel=new JPanel(new BorderLayout());
Icon icon = UIManager.getIcon("OptionPane.warningIcon");
pLabel.add(new JLabel(icon),BorderLayout.WEST);
JTextArea label=new JTextArea("The certificate sent by the server is unknown and not trustable!\n"+
"Do you want to continue creating a SSL connection to that server ?\n\n"+
"Note: If you answer 'Yes' the certificate will be stored in the file\n\n"+
keyStorePath+"\n\n"+
"and the next time treated trustable automatically. If you want to remove\n"+
"the certificate delete the file or use keytool to remove certificates\n"+
"selectively.");
label.setEditable(false);
label.setBackground(cont.getBackground());
label.setFont(label.getFont().deriveFont(Font.BOLD));
pLabel.add(label,BorderLayout.EAST);
GridBagConstraints gc=new GridBagConstraints();
gc.fill=GridBagConstraints.HORIZONTAL;
gl.setConstraints(pLabel,gc);
pLabel.setBorder(new EmptyBorder(4,4,4,4));
cont.add(pLabel);
Vector choices=new Vector();
for(int i=0; i<chain.length ; i++) {
choices.add((i+1)+". certificate of chain");
}
certChain = new JComboBox(choices);
certChain.setBackground(cont.getBackground());
certChain.setFont(label.getFont().deriveFont(Font.BOLD));
certChain.addActionListener(this);
JPanel pChoice=new JPanel(new BorderLayout());
pChoice.add(certChain);
gc=new GridBagConstraints();
gc.fill=GridBagConstraints.HORIZONTAL;
gc.insets=new Insets(4,4,4,4);
gc.gridy=1;
gl.setConstraints(pChoice,gc);
pChoice.setBorder(new TitledBorder(new EmptyBorder(0,0,0,0), "Certificate chain"));
cont.add(pChoice);
certItems=new JList(certParms);
certItems.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
certItems.addListSelectionListener(this);
JPanel pList=new JPanel(new BorderLayout());
pList.add(certItems);
pList.setBorder(new TitledBorder(new EtchedBorder(), "Certificate variables"));
gc=new GridBagConstraints();
gc.fill=GridBagConstraints.HORIZONTAL;
gc.insets=new Insets(4,4,4,4);
gc.gridy=2;
gl.setConstraints(pList,gc);
cont.add(pList);
certValues=new JTextArea();
certValues.setFont(label.getFont().deriveFont(Font.BOLD));
certValues.setEditable(false);
certValues.setBackground(cont.getBackground());
certValues.setLineWrap(true);
certValues.setWrapStyleWord(true);
JPanel pVals=new JPanel(new BorderLayout());
pVals.add(certValues);
pVals.setBorder(new TitledBorder(new EtchedBorder(), "Variable value"));
gc=new GridBagConstraints();
gc.insets=new Insets(4,4,4,4);
gc.weightx=1.0;
gc.weighty=1.0;
gc.fill=GridBagConstraints.BOTH;
gc.gridy=3;
gl.setConstraints(pVals,gc);
cont.add(pVals);
JPanel p=new JPanel();
yes.addActionListener(this);
no.addActionListener(this);
p.add(yes);
p.add(no);
gc=new GridBagConstraints();
gc.weightx=1.0;
gc.fill=GridBagConstraints.HORIZONTAL;
gc.gridy=4;
gl.setConstraints(p,gc);
cont.add(p);
//This should be the subject item
certItems.setSelectedIndex(5);
certItems.requestFocus();
trust.pack();
trust.setSize(500,600);
trust.setVisible(true);
return isTrusted;
}
/**
* Listener method for changin the contents of the JTextArea according to the
* selected list item.
*/
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()){
return;
}
JList theList = (JList)e.getSource();
if (theList.isSelectionEmpty()) {
certValues.setText("");
} else {
String selVal = theList.getSelectedValue().toString();
if(selVal.equals("Version")==true) {
certValues.setText(String.valueOf(chain[chainIdx].getVersion()));
} else if(selVal.equals("Serial Number")==true) {
certValues.setText(byteArrayToHex(chain[chainIdx].getSerialNumber().toByteArray()));
} else if(selVal.equals("Signature Algorithm")==true) {
certValues.setText(chain[chainIdx].getSigAlgName());
} else if(selVal.equals("Issuer")==true) {
certValues.setText(chain[chainIdx].getIssuerDN().getName());
} else if(selVal.equals("Validity Period")==true) {
certValues.setText(chain[chainIdx].getNotBefore().toString()+" - "+chain[chainIdx].getNotAfter().toString());
} else if(selVal.equals("Subject")==true) {
certValues.setText(chain[chainIdx].getSubjectDN().getName());
} else if(selVal.equals("Signature")==true) {
certValues.setText(byteArrayToHex(chain[chainIdx].getSignature()));
} else if(selVal.equals("Certificate Fingerprint")==true) {
try{
certValues.setText(getFingerprint(chain[chainIdx].getEncoded(),"MD5")+"\n"+
getFingerprint(chain[chainIdx].getEncoded(),"SHA1"));
}catch(Exception fingerE) {
certValues.setText("Couldn't calculate fingerprints of the certificate.\nReason: "+fingerE.getMessage());
}
}
}
}
/**
* This method calculates the fingerprint of the certificate. It takes the encoded form of
* the certificate and calculates a hash value from it.
*
* @param certificateBytes In: The byte array of the encoded data.
* @param algorithm In: The algorithm to be used for calculating the hash value.
* Two are possible: MD5 and SHA1
*
* @return: Returns a hex formated string of the fingerprint.
*/
private String getFingerprint(byte[] certificateBytes, String algorithm) throws Exception {
MessageDigest md = MessageDigest.getInstance(algorithm);
md.update(certificateBytes);
byte[] digest = md.digest();
return new String(algorithm+": "+byteArrayToHex(digest));
}
/**
* This method converts a byte array to a hex formatted string.
*
* @param byteData In: The data to be converted.
*
* @return: The formatted string.
*/
private String byteArrayToHex(byte[] byteData) {
StringBuffer sb=new StringBuffer();
for (int i = 0; i < byteData.length; i++) {
if (i != 0) sb.append(":");
int b = byteData[i] & 0xff;
String hex = Integer.toHexString(b);
if (hex.length() == 1) sb.append("0");
sb.append(hex);
}
return sb.toString();
}
/**
* The listener for the 'Yes', 'No' buttons.
*/
public void actionPerformed(ActionEvent e) {
Object entry =e.getSource();
if(entry.equals(yes)==true) {
isTrusted=true;
trust.dispose();
} else if(entry.equals(certChain)==true) {
int selIndex=certChain.getSelectedIndex();
if(selIndex >=0 && selIndex < chain.length) {
chainIdx=selIndex;
int oldSelIdx=certItems.getSelectedIndex();
certItems.clearSelection();
certItems.setSelectedIndex(oldSelIdx);
}
} else {
trust.dispose();
}
}
}
}
-----------------------------------------------end------------------------------------------------
然后我们在主类中通过以下语句调用:
SSLSocketFactory factory = null;
KeyManager[] km = null;
TrustManager[] tm = {new StoreCertTrustManager()};
SSLContext sslContext = SSLContext.getInstance("SSL","SunJSSE");
sslContext.init(null, tm, new java.security.SecureRandom());
factory = sslContext.getSocketFactory();
SSLSocket socket =
(SSLSocket)factory.createSocket("localhost", 7002);