ClientTrustManager.java 15.9 KB
Newer Older
1
/**
2
 * Copyright (C) 2004-2008 Jive Software. All rights reserved.
3
 *
4 5 6 7 8 9 10 11 12 13 14
 * 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.
15 16 17 18 19 20 21 22 23 24
 */

package org.jivesoftware.openfire.net;

import org.jivesoftware.util.CertificateManager;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.Log;

import javax.net.ssl.X509TrustManager;
import java.io.BufferedInputStream;
25
import java.io.File;
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.*;
import java.security.cert.*;
import java.util.*;

/**
 * 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 {

    /**
     * KeyStore that holds the trusted CA
     */
    private KeyStore trustStore;
    /**
     * Holds the domain of the remote server we are trying to connect
     */
    private String server;

    /**
     * Holds the CRL's to validate certs
     */
    private CertStore crlStore = null;
58 59 60 61 62
    
    /**
     * Holds the actual CRL's
     */
    private Collection<X509CRL> crls = null;
63

64 65 66 67 68
    /**
     * Last time the CRL file was updated 
     */
    private long crlLastUpdated = 0;
    
Jay Kline's avatar
Jay Kline committed
69 70 71 72 73
    /**
     * Should CRL checking be done
     */
    private boolean useCRLs = false;
    
74
    
75 76 77 78 79 80
    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.
81
        crls = new ArrayList<X509CRL>();
82
        CollectionCertStoreParameters params = new CollectionCertStoreParameters(crls);
83
        
84
        try {
85
            crlStore = CertStore.getInstance("Collection", params);
86
        }
87 88 89 90
        catch (InvalidAlgorithmParameterException ex) {
            Log.warn("ClientTrustManager: ",ex);
        } catch (NoSuchAlgorithmException ex) {
            Log.warn("ClientTrustManager: ",ex);
91
        }
92 93 94 95 96 97 98 99 100
          
        loadCRL();
       
    }
    
    private void loadCRL() {
        File crlFile = new File(JiveGlobals.getProperty("xmpp.client.certificate.crl",
                "resources" + File.separator + "security" + File.separator + "crl.pem"));
        
Jay Kline's avatar
Jay Kline committed
101
        
102
        if (!crlFile.isFile()) {
Jay Kline's avatar
Jay Kline committed
103 104
            Log.debug("ClientTrustmanager: crl file not found "+crlFile.toString());
            useCRLs = false;
105
            return;
106
        }
107
        
Jay Kline's avatar
Jay Kline committed
108
        
109 110 111 112
        long modified = crlFile.lastModified();
        if (modified > crlLastUpdated) {
            crlLastUpdated = modified;
            Log.debug("ClientTrustManager: Updating CRLs");
Jay Kline's avatar
Jay Kline committed
113
            useCRLs = false;
114 115 116 117 118 119 120 121 122 123 124 125 126 127
            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);
                }
Jay Kline's avatar
Jay Kline committed
128
                useCRLs = true;
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
            }
            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);
            }
145 146 147 148 149 150 151
        }
    }

    public void checkClientTrusted(X509Certificate[] x509Certificates, String string)
            throws CertificateException {
        Log.debug("ClientTrustManager: checkClientTrusted(x509Certificates,"+string+") called");

152
        loadCRL();
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
        ArrayList<X509Certificate> certs = new ArrayList<X509Certificate>();
        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.getPeerIdentities(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);
                }
                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();
Jay Kline's avatar
Jay Kline committed
260 261 262
                certSelector.setCertificate(x509Certificates[0]);
                PKIXBuilderParameters params = new PKIXBuilderParameters(trustStore,certSelector);
                if(useCRLs) {
263
                    params.addCertStore(crlStore);
Jay Kline's avatar
Jay Kline committed
264 265 266 267
                } else {
                    Log.debug("ClientTrustManager: no CRL's found, so setRevocationEnabled(false)");
                    params.setRevocationEnabled(false);
                }
268 269 270

                CertPathBuilderResult cpbr = cpb.build(params);
                CertPath cp = cpbr.getCertPath();
Jay Kline's avatar
Jay Kline committed
271 272 273 274 275
                if(JiveGlobals.getBooleanProperty("ocsp.enable",false)) {
                    Log.debug("ClientTrustManager: OCSP requested");
                    OCSPChecker ocspChecker = new OCSPChecker(cp,params);
                    params.addCertPathChecker(ocspChecker);
                }
276 277 278 279 280 281 282 283 284
                PKIXCertPathValidatorResult cpvResult = (PKIXCertPathValidatorResult) cpv.validate(cp, params);
                X509Certificate trustedCert = (X509Certificate) 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 e) {
Jay Kline's avatar
Jay Kline committed
285
                Log.debug("ClientTrustManager:",e);
286 287 288
                throw new CertificateException("certificate path failed: "+e.getMessage());
            }
            catch(CertPathValidatorException e) {
Jay Kline's avatar
Jay Kline committed
289
                Log.debug("ClientTrustManager:",e);
290 291
                throw new CertificateException("certificate path failed: "+e.getMessage());
            }
Jay Kline's avatar
Jay Kline committed
292 293 294
            catch(Exception e) {
                Log.debug("ClientTrustManager:",e);
                throw new CertificateException("unexpected error: "+e.getMessage());
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
            }
            
        }
    }

    /**
     * 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.
     */
    public void checkServerTrusted(X509Certificate[] x509Certificates, String string)
            throws CertificateException {

        Log.debug("ClientTrustManager: checkServerTrusted(...) called");

    }

    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);
                X509Certs = null;
            }
            return X509Certs;
        }
    }
}