我们使用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);

|