Custom TrustManager example which asks user for trustability
843811May 7 2002 — edited May 21 2002Hi!
For I needed a trustmanager which shows an untrusted server certificate to the user and asks him if the certificate can be considered trustable I wrote an own trustmanager. As it took much time to find out how to do this here is my code to shorten your time if you need a similar functionality:
NOTE:
If a certificate is declared trustable by the user it is stored in a local keystore file. So if you use the code in an applet you need to sign it to get the appropriate permissions.
To use it you have to put the following code where you get your SSLSocketFactory:
try{
SSLContext sslContext=SSLContext.getInstance("SSL","SunJSSE");
TrustManager[] tm={new StoreCertTrustManager()};
sslContext.init(null,tm,null);
factory = ( SSLSocketFactory) sslContext.getSocketFactory();
}catch(Exception e) { ... }
Here is the implementation of the StoreCertTrustManager:
/**
* 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.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 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[i]=cf[i];
} else {
allCfs[i]=(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 block