ClientTrustManager.java 17.1 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
 */

package org.jivesoftware.openfire.net;

import java.io.BufferedInputStream;
20
import java.io.File;
21 22 23
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
24 25 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 58
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;
59 60 61 62 63 64 65 66 67 68 69 70 71

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

72 73
	private static final Logger Log = LoggerFactory.getLogger(ClientTrustManager.class);

74 75 76 77 78 79 80 81 82 83 84 85 86
    /**
     * 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;
87 88 89 90 91
    
    /**
     * Holds the actual CRL's
     */
    private Collection<X509CRL> crls = null;
92

93 94 95 96 97
    /**
     * Last time the CRL file was updated 
     */
    private long crlLastUpdated = 0;
    
Jay Kline's avatar
Jay Kline committed
98 99 100 101 102
    /**
     * Should CRL checking be done
     */
    private boolean useCRLs = false;
    
103
    
104 105 106 107 108 109
    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.
110
        crls = new ArrayList<X509CRL>();
111
        CollectionCertStoreParameters params = new CollectionCertStoreParameters(crls);
112
        
113
        try {
114
            crlStore = CertStore.getInstance("Collection", params);
115
        }
116 117 118 119
        catch (InvalidAlgorithmParameterException ex) {
            Log.warn("ClientTrustManager: ",ex);
        } catch (NoSuchAlgorithmException ex) {
            Log.warn("ClientTrustManager: ",ex);
120
        }
121 122 123 124 125 126 127 128 129
          
        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
130
        
131
        if (!crlFile.isFile()) {
Jay Kline's avatar
Jay Kline committed
132 133
            Log.debug("ClientTrustmanager: crl file not found "+crlFile.toString());
            useCRLs = false;
134
            return;
135
        }
136
        
Jay Kline's avatar
Jay Kline committed
137
        
138 139 140 141
        long modified = crlFile.lastModified();
        if (modified > crlLastUpdated) {
            crlLastUpdated = modified;
            Log.debug("ClientTrustManager: Updating CRLs");
Jay Kline's avatar
Jay Kline committed
142
            useCRLs = false;
143 144 145 146 147 148 149 150 151 152 153 154 155 156
            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
157
                useCRLs = true;
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
            }
            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);
            }
174 175 176 177 178 179 180
        }
    }

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

181
        loadCRL();
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 260
        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) {
261
                    Log.error(e.getMessage(), e);
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
                }
                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
289 290 291
                certSelector.setCertificate(x509Certificates[0]);
                PKIXBuilderParameters params = new PKIXBuilderParameters(trustStore,certSelector);
                if(useCRLs) {
292
                    params.addCertStore(crlStore);
Jay Kline's avatar
Jay Kline committed
293 294 295 296
                } else {
                    Log.debug("ClientTrustManager: no CRL's found, so setRevocationEnabled(false)");
                    params.setRevocationEnabled(false);
                }
297 298 299

                CertPathBuilderResult cpbr = cpb.build(params);
                CertPath cp = cpbr.getCertPath();
Jay Kline's avatar
Jay Kline committed
300 301 302 303 304
                if(JiveGlobals.getBooleanProperty("ocsp.enable",false)) {
                    Log.debug("ClientTrustManager: OCSP requested");
                    OCSPChecker ocspChecker = new OCSPChecker(cp,params);
                    params.addCertPathChecker(ocspChecker);
                }
305 306 307 308 309 310 311 312 313
                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
314
                Log.debug("ClientTrustManager:",e);
315 316 317
                throw new CertificateException("certificate path failed: "+e.getMessage());
            }
            catch(CertPathValidatorException e) {
Jay Kline's avatar
Jay Kline committed
318
                Log.debug("ClientTrustManager:",e);
319 320
                throw new CertificateException("certificate path failed: "+e.getMessage());
            }
Jay Kline's avatar
Jay Kline committed
321 322 323
            catch(Exception e) {
                Log.debug("ClientTrustManager:",e);
                throw new CertificateException("unexpected error: "+e.getMessage());
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 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
            }
            
        }
    }

    /**
     * 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) {
387
                Log.error(e.getMessage(), e);
388 389 390 391 392 393
                X509Certs = null;
            }
            return X509Certs;
        }
    }
}