/* Leech - crawling capabilities for Apache Tika Copyright (C) 2012 DFKI GmbH, Author: Christian Reuschling This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. Contact us by mail: christian.reuschling@dfki.de */ /* * Copyright (c) 2005 - 2008 Aduna. * All rights reserved. * * Licensed under the Aperture BSD-style license. */ package de.dfki.km.leech.util.certificates; import java.io.File; import java.io.IOException; 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.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A default implementation, well-suited for most environments, of X509TrustManager. It handles all * certificates that can be validated by the system certificates and uses a delegate mechanism to decide * what to do with unknown certificates. Such a delegate may for example show a dialog asking the user * what to do, similar to what web browsers and mail readers typically do. */ public class StandardTrustManager implements X509TrustManager { private Logger logger = LoggerFactory.getLogger(getClass()); /** * The file name of the standard root certificates file. */ private static final String ROOT_CERTIFICATES_FILE_NAME; /** * The file name of the JSSE root certificates file. */ private static final String JSSE_ROOT_CERTIFICATES_FILE_NAME; static { // determine where the root certificates and HTTPS root certificates are stored String securityPath = System.getProperty("java.home") + File.separator + "lib" + File.separator + "security" + File.separator; ROOT_CERTIFICATES_FILE_NAME = securityPath + "cacerts"; JSSE_ROOT_CERTIFICATES_FILE_NAME = securityPath + "jssecacerts"; } /** * The CertificateStore that holds all Certificates approved for this session. */ private CertificateStore sessionCertificateStore; /** * The CertificateStore that holds all Certificates denied for this session. */ private CertificateStore deniedCertificateStore; /** * The CertificateStore that holds all standard root certificates. */ private CertificateStore rootCertificateStore; /** * The CertificateStore that holds all JSSE root certificates. */ private CertificateStore jsseRootCertificateStore; /** * The CertificateStore that holds all permanently approved certificates. */ private CertificateStore persistentCertificateStore; /** * A system-provided TrustManager used to verify a chain of certificates before checking the chain * against our own CertificateStores. This is only overruled by the CertificateStores holding the * denied certificates. */ private X509TrustManager defaultTrustManager; /** * The delegate used to decide on how to judge a Certificate when it cannot be verified using the * default trust manager or one of the CertificateStores. */ private TrustDecider trustDecider; /** * Create a StandardTrustManager that has no persistent storage for permanently approved * certificates. */ public StandardTrustManager() throws CertificateException, KeyStoreException, NoSuchAlgorithmException, NoSuchProviderException, IOException { this(null, null); } /** * Create a StandardTrustManager that uses the specified File to store its permanently approved * certificates. * * @param pcsFile A File to load and store the certificates, or null when no certificates should be * loaded and stored. * @param pcsPassword The password used to check the integrity of the keystore, the password used to * unlock the keystore, or null. */ public StandardTrustManager(File pcsFile, char[] pcsPassword) throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException, NoSuchProviderException { // create the certificate stores sessionCertificateStore = new SessionCertificateStore(); deniedCertificateStore = new SessionCertificateStore(); rootCertificateStore = new RootCertificateStore(ROOT_CERTIFICATES_FILE_NAME); jsseRootCertificateStore = new RootCertificateStore(JSSE_ROOT_CERTIFICATES_FILE_NAME); if (pcsFile == null) { persistentCertificateStore = new SessionCertificateStore(); } else { persistentCertificateStore = new PersistentCertificateStore(pcsFile, pcsPassword); } // let all CertificateStores load their Certificates (some may choose to ignore this operation) sessionCertificateStore.load(); deniedCertificateStore.load(); rootCertificateStore.load(); jsseRootCertificateStore.load(); persistentCertificateStore.load(); // create an instance of the default TrustManager TrustManagerFactory factory = TrustManagerFactory.getInstance("SunX509", "SunJSSE"); factory.init((KeyStore) null); TrustManager[] managers = factory.getTrustManagers(); defaultTrustManager = (X509TrustManager) managers[0]; } public void setTrustDecider(TrustDecider trustDecider) { this.trustDecider = trustDecider; } public TrustDecider getTrustDecider() { return trustDecider; } @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { checkChain(chain, authType, true); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { checkChain(chain, authType, false); } @Override public X509Certificate[] getAcceptedIssuers() { return defaultTrustManager.getAcceptedIssuers(); } private synchronized void checkChain(X509Certificate[] chain, String authType, boolean checkClient) throws CertificateException { boolean rootCANotValid = false; boolean timeNotValid = false; Decision decision = null; try { // check if the certificate has been denied before (overrules the default trust manager) if (deniedCertificateStore.contains(chain[0])) { throw new CertificateException("certificate has been denied"); } // let the default trust manager inspect the certificates try { if (checkClient) { defaultTrustManager.checkClientTrusted(chain, authType); } else { defaultTrustManager.checkServerTrusted(chain, authType); } // no exceptions while checking the certificates, so they are ok return; } catch (CertificateException e) { // certificate verification failed, proceed } // check if the certificate has been accepted for this session before if (sessionCertificateStore.contains(chain[0])) { return; } // check if the certificate has been permanently accepted before if (persistentCertificateStore.contains(chain[0])) { return; } // loop through all the certs in chain for (int i = 0; i < chain.length; i++) { X509Certificate cert = chain[i]; // check if one of the certificates in the chain could not be verified if (!jsseRootCertificateStore.verify(cert) && !rootCertificateStore.verify(cert)) rootCANotValid = true; // Check if the cert has expired. try { cert.checkValidity(); } catch (CertificateExpiredException e) { timeNotValid = true; } catch (CertificateNotYetValidException e) { timeNotValid = true; } } // let the TrustDecider decise TrustDecider trustDecider = getTrustDecider(); if (trustDecider == null) { throw new CertificateException("trust could not be established"); } else { decision = trustDecider.decide(chain, rootCANotValid, timeNotValid); X509Certificate cert = chain[0]; if (Decision.TRUST_THIS_SESSION.equals(decision)) { sessionCertificateStore.add(cert); sessionCertificateStore.save(); } else if (Decision.TRUST_ALWAYS.equals(decision)) { persistentCertificateStore.add(cert); persistentCertificateStore.save(); } else { deniedCertificateStore.add(cert); deniedCertificateStore.save(); } } } catch (CertificateException e) { throw e; } catch (Throwable e) { // a new exception is thrown below, no need to throw one here logger.error("Unexpected throwable while verifying certificate", e); } if (!Decision.TRUST_THIS_SESSION.equals(decision) && !Decision.TRUST_ALWAYS.equals(decision)) { throw new CertificateException("trust manager could not trust certificate chain"); } } }