Java tutorial
// Copyright 2007 Hitachi Data Systems // All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package com.archivas.clienttools.arcutils.utils.net; import com.archivas.clienttools.arcutils.config.ConfigurationHelper; import com.archivas.clienttools.arcutils.profile.AbstractProfileBase; import com.archivas.clienttools.arcutils.profile.HCAPProfile; import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier; import javax.net.ssl.SSLException; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; import java.io.*; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.text.MessageFormat; import java.util.Iterator; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * Reference Material: * http://java.sun.com/j2se/1.5.0/docs/guide/security/jsse/JSSERefGuide.html#TrustManager * http://hc.apache.org/httpcomponents-client/tutorial/html/connmgmt.html#d4e497 * <p> * EasyX509TrustManager unlike default {@link javax.net.ssl.X509TrustManager} accepts self-signed * certificates. * </p> */ public class GetCertsX509TrustManager implements X509TrustManager { public static final String PACKAGE_NAME = GetCertsX509TrustManager.class.getPackage().getName(); public static final String CLASS_FULL_NAME = GetCertsX509TrustManager.class.getName(); public static final String CLASS_NAME = CLASS_FULL_NAME.substring(PACKAGE_NAME.length() + 1); public static Logger LOG = Logger.getLogger(CLASS_FULL_NAME); private static File persistedKeystoreFile = null; private static char[] persistedKeystorePassword = "password".toCharArray(); private X509TrustManager standardTrustManager = null; private static X509TrustManager persistedTrustManager = null; private static KeyStore persistedKeyStore = null; private static X509TrustManager memoryTrustManager = null; private static KeyStore memoryKeyStore = null; private GetCertsCallback callback = null; private SSLCertificateCallback sslExceptionCallback = null; private String hostname = null; private AbstractProfileBase profile = null; private static final Object LOCK = new Object(); public GetCertsX509TrustManager(HCAPProfile profile, final SSLCertificateCallback sslExceptionCallback) throws NoSuchAlgorithmException, NoSuchProviderException, KeyStoreException { super(); this.profile = profile; this.hostname = profile.getHostname(); // getHostname returns the hostname provided in the // profile wizard synchronized (LOCK) { this.sslExceptionCallback = sslExceptionCallback; LOG.log(Level.FINER, "Entering GetCertsX509TrustManager(Keystore, callback)"); initStandardTrustManager(null); initPersistedTrustManager(); initMemoryTrustManager(); if (standardTrustManager == null && persistedTrustManager == null && memoryTrustManager == null) { throw new NoSuchAlgorithmException("no trust manager found"); } } } public void initStandardTrustManager(KeyStore keystore) throws NoSuchAlgorithmException, NoSuchProviderException, KeyStoreException { TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); factory.init(keystore); TrustManager[] trustmanagers = factory.getTrustManagers(); // Iterate over the returned trustmanagers, look for an instance of X509TrustManager. // If found, use that as our "default" trust manager. for (int i = 0; i < trustmanagers.length; i++) { if (trustmanagers[i] instanceof X509TrustManager) { if (standardTrustManager == null) { standardTrustManager = (X509TrustManager) trustmanagers[i]; } // break; LOG.log(Level.FINER, "standardTrustManager=" + trustmanagers[i]); } } } public void initPersistedTrustManager() throws NoSuchAlgorithmException, NoSuchProviderException, KeyStoreException { initPersistedTrustManager(false); } public void initPersistedTrustManager(boolean forcereload) throws NoSuchAlgorithmException, NoSuchProviderException, KeyStoreException { if (persistedTrustManager != null && !forcereload) { return; } String homedir = System.getProperty("user.home"); String fileNameTemplate = ConfigurationHelper.USER_CONFIG_DIRECTORY + ConfigurationHelper.getStringProperty("ssl.keystore.filename", "cacerts"); String fileName = MessageFormat.format(fileNameTemplate, homedir); persistedKeystoreFile = new File(fileName); try { persistedKeyStore = KeyStore.getInstance("JKS"); try { FileInputStream fis = null; if (persistedKeystoreFile.exists()) { fis = new FileInputStream(persistedKeystoreFile); } persistedKeyStore.load(fis, persistedKeystorePassword); } catch (FileNotFoundException e) { // Don't Care. Go on. LOG.log(Level.WARNING, "Unexpected Exception", e); } catch (IOException e) { LOG.log(Level.WARNING, "Unexpected Exception", e); } catch (CertificateException e) { LOG.log(Level.WARNING, "Unexpected Exception", e); } TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(persistedKeyStore); TrustManager tms[] = tmf.getTrustManagers(); // Iterate over the returned trustmanagers, look for an instance of X509TrustManager. // If found, use that as our "default" trust manager. for (int i = 0; i < tms.length; i++) { if (tms[i] instanceof X509TrustManager) { persistedTrustManager = (X509TrustManager) tms[i]; break; } } LOG.log(Level.FINER, "persistedTrustManager=" + persistedTrustManager); } catch (KeyStoreException e) { LOG.log(Level.WARNING, "Unexpected Exception", e); throw e; } catch (NoSuchAlgorithmException e) { LOG.log(Level.WARNING, "Unexpected Exception", e); throw e; } catch (RuntimeException e) { LOG.log(Level.WARNING, "Unexpected Exception", e); throw e; } } public void initMemoryTrustManager() throws NoSuchAlgorithmException, NoSuchProviderException, KeyStoreException { initMemoryTrustManager(false); } public void initMemoryTrustManager(boolean forcereload) throws NoSuchAlgorithmException, NoSuchProviderException, KeyStoreException { if (memoryTrustManager != null && !forcereload) { return; } try { if (memoryKeyStore == null) { memoryKeyStore = KeyStore.getInstance("JKS"); } try { memoryKeyStore.load(null, persistedKeystorePassword); } catch (IOException e) { LOG.log(Level.WARNING, "Unexpected Exception", e); } catch (CertificateException e) { LOG.log(Level.WARNING, "Unexpected Exception", e); } TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(memoryKeyStore); TrustManager tms[] = tmf.getTrustManagers(); // Iterate over the returned trustmanagers, look for an instance of X509TrustManager. // If found, use that as our "default" trust manager. for (int i = 0; i < tms.length; i++) { if (tms[i] instanceof X509TrustManager) { memoryTrustManager = (X509TrustManager) tms[i]; break; } } LOG.log(Level.FINER, "MemoryTrustManager=" + memoryTrustManager); } catch (KeyStoreException e) { LOG.log(Level.WARNING, "Unexpected Exception", e); throw e; } catch (NoSuchAlgorithmException e) { LOG.log(Level.WARNING, "Unexpected Exception", e); throw e; } catch (RuntimeException e) { LOG.log(Level.WARNING, "Unexpected Exception", e); throw e; } } /** * @see javax.net.ssl.X509TrustManager#checkClientTrusted(java.security.cert.X509Certificate[],String * authType) */ public void checkClientTrusted(X509Certificate[] certificates, String authType) throws CertificateException { SSLCertChain certChain = new SSLCertChain(authType, certificates); synchronized (LOCK) { if (callback != null) { callback.callback(certChain); } try { standardTrustManager.checkClientTrusted(certificates, authType); } catch (CertificateException certException) { // do any special handling here, or rethrow exception. if (callback != null) { callback.callback(new SSLCertChain(authType, certificates, certException)); } LOG.log(Level.WARNING, "Error checking Client Trust", certException); throw certException; } catch (RuntimeException certException) { // do any special handling here, or rethrow exception. LOG.log(Level.WARNING, "Error checking Client Trust", certException); throw certException; } } } /** * @see javax.net.ssl.X509TrustManager#checkServerTrusted(java.security.cert.X509Certificate[],String * authType) */ public void checkServerTrusted(X509Certificate[] certificates, String authType) throws CertificateException { synchronized (LOCK) { try { if ((certificates != null) && LOG.isLoggable(Level.FINER)) { LOG.log(Level.FINER, "Server certificate chain:"); for (int i = 0; i < certificates.length; i++) { LOG.log(Level.FINER, "X509Certificate[" + i + "]=" + certificates[i]); } } checkServerTrusted1(certificates, authType); } catch (CertificateException e) { LOG.log(Level.FINEST, "Exception checking trust for certificate.", e); throw e; } catch (RuntimeException e) { LOG.log(Level.WARNING, "Unexpected exception checking trust for certificate.", e); throw e; } } } public String testHostname(String hostname, SSLCertChain certChain) { String result = null; String testingCN = null; try { List<X509Certificate> certList = certChain.getCertificateList(); String[] cnList = new String[certList.size()]; Iterator<X509Certificate> i = certList.iterator(); for (int count = 0; i.hasNext(); ++count) { String dn = ((X509Certificate) i.next()).getSubjectDN().getName(); int cnIndex = dn.indexOf("CN=") + 3; if (cnIndex < 0) { LOG.log(Level.FINE, "Hostname not found in certificate " + dn); continue; } int cnEndIndex = dn.indexOf(',', cnIndex); String cn = (cnEndIndex < 0 ? dn.substring(cnIndex + 3) : dn.substring(cnIndex + 3, cnEndIndex)); // Also remove the *. if (cn.startsWith("*.")) { cn = cn.substring(2); } cnList[count] = cn; // I think it is unlikely there are ever multiple certs coming in here. testingCN = cn; } BrowserCompatHostnameVerifier verifier = new BrowserCompatHostnameVerifier(); verifier.verify(hostname, cnList, null); } catch (SSLException e) { if (testingCN != null) { result = "Host name " + hostname + " is not equal to the certificate issuer's \nhost name " + testingCN; } LOG.log(Level.FINE, e.getMessage(), e); } return result; } public void checkServerTrusted1(X509Certificate[] certificates, String authType) throws CertificateException { SSLCertChain certChain = new SSLCertChain(authType, certificates); // Try the in-memory, session only trust manager first try { LOG.log(Level.FINEST, "About to have MemoryTrustManager Test cert: " + certChain); memoryTrustManager.checkServerTrusted(certificates, authType); LOG.log(Level.FINEST, "MemoryTrustManager Approves of cert: " + certChain); if (sslExceptionCallback != null) { sslExceptionCallback.validCertCallback(profile, certChain); } return; } catch (Exception certException) { // Log it and fall through to the next trust mgr. LOG.log(Level.FINE, "Error checking Server Trust via MemoryTrustManager: " + certException.getMessage()); LOG.log(Level.FINER, "Error checking Server Trust via MemoryTrustManager", certException); } // Try the persisted to disk trust manager next try { LOG.log(Level.FINEST, "About to have PersistedTrustManager Test cert: " + certChain); persistedTrustManager.checkServerTrusted(certificates, authType); LOG.log(Level.FINEST, "PersistedTrustManager Approves of cert: " + certChain); if (sslExceptionCallback != null) { sslExceptionCallback.validCertCallback(profile, certChain); } return; } catch (Exception certException) { // Log it and fall through to the next trust mgr. LOG.log(Level.FINE, "Error checking Server Trust via PersistedTrustManager: " + certException.getMessage()); LOG.log(Level.FINER, "Error checking Server Trust via PersistedTrustManager", certException); } // Try the default JDK trust manager last. (This has the default JDK root certs + any the // user has added) try { LOG.log(Level.FINEST, "About to have standardTrustManager Test cert: " + certChain); standardTrustManager.checkServerTrusted(certificates, authType); LOG.log(Level.FINEST, "standardTrustManager Approves of cert: " + certChain); /* * if ((certificates != null) && (certificates.length == 1)) { * certificates[0].checkValidity(); } else { * standardTrustManager.checkServerTrusted(certificates,authType); } */ if (sslExceptionCallback != null) { sslExceptionCallback.validCertCallback(profile, certChain); } } catch (CertificateException certException) { LOG.log(Level.FINE, "Error checking Server Trust via standardTrustManager: " + certException.getMessage()); LOG.log(Level.FINER, "Error checking Server Trust via standardTrustManager", certException); handleCertFailureCallback(certChain, certException); } } protected void handleCertFailureCallback(SSLCertChain certChain, CertificateException certException) throws CertificateException { // We need to warn them if the hostname is invalid String hostNameWarningString = null; if (hostname != null) { hostNameWarningString = testHostname(hostname, certChain); } SSLCertificateCallback.AllowSSLCert allowAccess = SSLCertificateCallback.AllowSSLCert.NO; certChain.setCertificateException(certException); if (sslExceptionCallback != null) { try { allowAccess = sslExceptionCallback.exceptionCallback(profile, certChain, hostNameWarningString); LOG.log(Level.FINE, "SSLExceptionCallback returned from user: " + allowAccess); } catch (Exception e) { LOG.log(Level.WARNING, "Unexpected Exception in SSL Certificate Exception Callback. Disallowing use of certificate: " + certChain, e); allowAccess = SSLCertificateCallback.AllowSSLCert.NO; } } if (allowAccess == SSLCertificateCallback.AllowSSLCert.THIS_SESSION_ONLY) { // User accepted the certificate. persist it to the keystore. try { LOG.log(Level.FINER, "About to store certificate in memory keystore: " + certChain); memoryKeyStore.setCertificateEntry(certChain.getIssuedByCommonName(), certChain.getCertificateList().get(0)); initMemoryTrustManager(true); LOG.log(Level.FINER, "Successfully stored cert into memory keystore: " + certChain); } catch (Exception e) { LOG.log(Level.WARNING, "Error saving certificate to memory keystore", e); } } else if (allowAccess == SSLCertificateCallback.AllowSSLCert.PERMANENTLY_SAVE) { // User accepted the certificate. persist it to the keystore. try { LOG.log(Level.FINER, "About to store certificate in persisted keystore: " + certChain); persistedKeyStore.setCertificateEntry(certChain.getIssuedByCommonName(), certChain.getCertificateList().get(0)); FileOutputStream fos = new FileOutputStream(persistedKeystoreFile); persistedKeyStore.store(fos, persistedKeystorePassword); initPersistedTrustManager(true); LOG.log(Level.FINER, "Successfully stored cert into persisted keystore: " + certChain); } catch (Exception e) { LOG.log(Level.WARNING, "Error saving certificate to persisted keystore", e); } } else { // If AllowSSLCert.NO or anything else, throw the exception throw certException; } // If we got here, The user accepted the cert and it is stored. Do the callback. if (sslExceptionCallback != null) { sslExceptionCallback.validCertCallback(profile, certChain); } } /** * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers() */ public X509Certificate[] getAcceptedIssuers() { return standardTrustManager.getAcceptedIssuers(); } }