/* * Copyright (C) 2004-2008 Jive Software. 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 org.jivesoftware.openfire.net; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.security.PublicKey; import java.security.cert.CRLException; import java.security.cert.CertPath; import java.security.cert.CertPathBuilder; import java.security.cert.CertPathBuilderException; import java.security.cert.CertPathBuilderResult; import java.security.cert.CertPathValidator; import java.security.cert.CertPathValidatorException; import java.security.cert.CertStore; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.CollectionCertStoreParameters; import java.security.cert.PKIXBuilderParameters; import java.security.cert.PKIXCertPathValidatorResult; import java.security.cert.X509CRL; import java.security.cert.X509CertSelector; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.Enumeration; import java.util.List; import javax.net.ssl.X509TrustManager; import org.jivesoftware.util.CertificateManager; import org.jivesoftware.util.JiveGlobals; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * ClientTrustManager is a Trust Manager that is only used for c2s connections. This TrustManager * is used both when a client connects to this server. It is possible to indicate if self-signed * certificates are going to be accepted. In case of accepting a self-signed certificate a warning * is logged. Future version of the server might include a small workflow so admins can review * self-signed certificates or certificates of unknown issuers and manually accept them. * * @author Gaston Dombiak * @author Jay Kline */ public class ClientTrustManager implements X509TrustManager { private static final Logger Log = LoggerFactory.getLogger(ClientTrustManager.class); /** * KeyStore that holds the trusted CA */ private KeyStore trustStore; /** * Holds the CRL's to validate certs */ private CertStore crlStore = null; /** * Holds the actual CRL's */ private Collection<X509CRL> crls = null; /** * Last time the CRL file was updated */ private long crlLastUpdated = 0; /** * Should CRL checking be done */ private boolean useCRLs = false; public ClientTrustManager(KeyStore trustTrust) { super(); this.trustStore = trustTrust; //Note: A reference of the Collection is used in the CertStore, so we can add CRL's // after creating the CertStore. crls = new ArrayList<>(); CollectionCertStoreParameters params = new CollectionCertStoreParameters(crls); try { crlStore = CertStore.getInstance("Collection", params); } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException ex) { Log.warn("ClientTrustManager: ",ex); } loadCRL(); } private void loadCRL() { File crlFile = new File(JiveGlobals.getProperty("xmpp.client.certificate.crl", "resources" + File.separator + "security" + File.separator + "crl.pem")); if (!crlFile.isFile()) { Log.debug("ClientTrustmanager: crl file not found "+crlFile.toString()); useCRLs = false; return; } long modified = crlFile.lastModified(); if (modified > crlLastUpdated) { crlLastUpdated = modified; Log.debug("ClientTrustManager: Updating CRLs"); useCRLs = false; try { CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509CRL crl; FileInputStream crlStream = new FileInputStream(crlFile); BufferedInputStream crlBuffer = new BufferedInputStream(crlStream); crls.clear(); //remove existing CRLs while (crlBuffer.available() > 0) { crl = (X509CRL)cf.generateCRL(crlBuffer); Log.debug("ClientTrustManager: adding CRL for "+crl.getIssuerDN()); crls.add(crl); } useCRLs = true; } catch(FileNotFoundException e) { // Its ok if the file wasnt found- maybe we dont have any CRL's Log.debug("ClientTrustManager: CRL file not found: "+crlFile.toString()); } catch(IOException e) { //Thrown bot the input streams Log.error("ClientTrustManager: IOException while parsing CRLs", e); } catch(CertificateException e) { //Thrown by CertificateFactory.getInstance(...) Log.error("ClientTrustManager: ",e); } catch(CRLException e) { Log.error("ClientTrustManager: CRLException while parsing CRLs", e); } } } @Override public void checkClientTrusted(X509Certificate[] x509Certificates, String string) throws CertificateException { Log.debug("ClientTrustManager: checkClientTrusted(x509Certificates,"+string+") called"); loadCRL(); ArrayList<X509Certificate> certs = new ArrayList<>(); for(int i = 0; i < x509Certificates.length ; i++) { certs.add(x509Certificates[i]); } boolean verify = JiveGlobals.getBooleanProperty("xmpp.client.certificate.verify", true); if (verify) { int nSize = x509Certificates.length; List<String> peerIdentities = CertificateManager.getClientIdentities(x509Certificates[0]); if (JiveGlobals.getBooleanProperty("xmpp.client.certificate.verify.chain", true)) { // Working down the chain, for every certificate in the chain, // verify that the subject of the certificate is the issuer of the // next certificate in the chain. Principal principalLast = null; for (int i = nSize -1; i >= 0 ; i--) { X509Certificate x509certificate = x509Certificates[i]; Principal principalIssuer = x509certificate.getIssuerDN(); Principal principalSubject = x509certificate.getSubjectDN(); if (principalLast != null) { if (principalIssuer.equals(principalLast)) { try { PublicKey publickey = x509Certificates[i + 1].getPublicKey(); x509Certificates[i].verify(publickey); } catch (GeneralSecurityException generalsecurityexception) { throw new CertificateException( "signature verification failed of " + peerIdentities); } } else { throw new CertificateException( "subject/issuer verification failed of " + peerIdentities); } } principalLast = principalSubject; } } if (JiveGlobals.getBooleanProperty("xmpp.client.certificate.verify.root", true)) { // Verify that the the last certificate in the chain was issued // by a third-party that the client trusts, or is trusted itself boolean trusted = false; try { Enumeration<String> aliases = trustStore.aliases(); while(aliases.hasMoreElements()) { String alias = aliases.nextElement(); X509Certificate tCert = (X509Certificate) trustStore.getCertificate(alias); if(x509Certificates[nSize - 1].equals(tCert)) { try { PublicKey publickey = tCert.getPublicKey(); x509Certificates[nSize -1].verify(publickey); } catch (GeneralSecurityException generalsecurityexception) { throw new CertificateException( "signature verification failed of " + peerIdentities); } trusted = true; break; } else { if(x509Certificates[nSize - 1].getIssuerDN().equals(tCert.getSubjectDN())) { try { PublicKey publickey = tCert.getPublicKey(); x509Certificates[nSize -1].verify(publickey); } catch (GeneralSecurityException generalsecurityexception) { throw new CertificateException( "signature verification failed of " + peerIdentities); } trusted = true; break; } } } } catch (KeyStoreException e) { Log.error(e.getMessage(), e); } if (!trusted) { //Log.debug("certificate not trusted of "+peerIdentities); throw new CertificateException("root certificate not trusted of " + peerIdentities); } } if (JiveGlobals.getBooleanProperty("xmpp.client.certificate.verify.validity", true)) { // For every certificate in the chain, verify that the certificate // is valid at the current time. Date date = new Date(); for (int i = 0; i < nSize; i++) { try { x509Certificates[i].checkValidity(date); } catch (GeneralSecurityException generalsecurityexception) { throw new CertificateException("invalid date of " + peerIdentities); } } } //Verify certificate path try { CertPathValidator cpv = CertPathValidator.getInstance("PKIX"); CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX"); X509CertSelector certSelector = new X509CertSelector(); certSelector.setCertificate(x509Certificates[0]); PKIXBuilderParameters params = new PKIXBuilderParameters(trustStore,certSelector); if(useCRLs) { params.addCertStore(crlStore); } else { Log.debug("ClientTrustManager: no CRL's found, so setRevocationEnabled(false)"); params.setRevocationEnabled(false); } CertPathBuilderResult cpbr = cpb.build(params); CertPath cp = cpbr.getCertPath(); if(JiveGlobals.getBooleanProperty("ocsp.enable",false)) { Log.debug("ClientTrustManager: OCSP requested"); OCSPChecker ocspChecker = new OCSPChecker(cp,params); params.addCertPathChecker(ocspChecker); } PKIXCertPathValidatorResult cpvResult = (PKIXCertPathValidatorResult) cpv.validate(cp, params); X509Certificate trustedCert = cpvResult.getTrustAnchor().getTrustedCert(); if(trustedCert == null) { throw new CertificateException("certificate path failed: Trusted CA is NULL"); } else { Log.debug("ClientTrustManager: Trusted CA: "+trustedCert.getSubjectDN()); } } catch(CertPathBuilderException | CertPathValidatorException e) { Log.debug("ClientTrustManager:",e); throw new CertificateException("certificate path failed: "+e.getMessage()); } catch(Exception e) { Log.debug("ClientTrustManager:",e); throw new CertificateException("unexpected error: "+e.getMessage()); } } } /** * Given the partial or complete certificate chain provided by the peer, build a certificate * path to a trusted root and return if it can be validated and is trusted for server SSL * authentication based on the authentication type. The authentication type is the key * exchange algorithm portion of the cipher suites represented as a String, such as "RSA", * "DHE_DSS". Note: for some exportable cipher suites, the key exchange algorithm is * determined at run time during the handshake. For instance, for * TLS_RSA_EXPORT_WITH_RC4_40_MD5, the authType should be RSA_EXPORT when an ephemeral * RSA key is used for the key exchange, and RSA when the key from the server certificate * is used. Checking is case-sensitive.<p> * * By default certificates are going to be verified. This includes verifying the certificate * chain, the root certificate and the certificates validity. However, it is possible to * disable certificates validation as a whole or each specific validation. * * @param x509Certificates an ordered array of peer X.509 certificates with the peer's own * certificate listed first and followed by any certificate authorities. * @param string the key exchange algorithm used. * @throws CertificateException if the certificate chain is not trusted by this TrustManager. */ @Override public void checkServerTrusted(X509Certificate[] x509Certificates, String string) throws CertificateException { Log.debug("ClientTrustManager: checkServerTrusted(...) called"); } @Override public X509Certificate[] getAcceptedIssuers() { if (JiveGlobals.getBooleanProperty("xmpp.client.certificate.accept-selfsigned", false)) { // Answer an empty list since we accept any issuer return new X509Certificate[0]; } else { X509Certificate[] X509Certs = null; try { // See how many certificates are in the keystore. int numberOfEntry = trustStore.size(); // If there are any certificates in the keystore. if (numberOfEntry > 0) { // Create an array of X509Certificates X509Certs = new X509Certificate[numberOfEntry]; // Get all of the certificate alias out of the keystore. Enumeration aliases = trustStore.aliases(); // Retrieve all of the certificates out of the keystore // via the alias name. int i = 0; while (aliases.hasMoreElements()) { X509Certs[i] = (X509Certificate) trustStore. getCertificate((String) aliases.nextElement()); i++; } } } catch (Exception e) { Log.error(e.getMessage(), e); X509Certs = null; } return X509Certs; } } }