ClientTrustManager.java 16.8 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<>();
111
        CollectionCertStoreParameters params = new CollectionCertStoreParameters(crls);
112
        
113
        try {
114
            crlStore = CertStore.getInstance("Collection", params);
115
        }
116
        catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException ex) {
117
            Log.warn("ClientTrustManager: ",ex);
118
        }
119

120 121 122 123 124 125 126 127
        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
128
        
129
        if (!crlFile.isFile()) {
Jay Kline's avatar
Jay Kline committed
130 131
            Log.debug("ClientTrustmanager: crl file not found "+crlFile.toString());
            useCRLs = false;
132
            return;
133
        }
134
        
Jay Kline's avatar
Jay Kline committed
135
        
136 137 138 139
        long modified = crlFile.lastModified();
        if (modified > crlLastUpdated) {
            crlLastUpdated = modified;
            Log.debug("ClientTrustManager: Updating CRLs");
Jay Kline's avatar
Jay Kline committed
140
            useCRLs = false;
141
            try {
142
                CertificateFactory cf = CertificateFactory.getInstance("X.509");
143 144 145 146 147 148 149 150 151 152 153 154

                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
155
                useCRLs = true;
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
            }
            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);
            }
172 173 174
        }
    }

175
    @Override
176 177 178 179
    public void checkClientTrusted(X509Certificate[] x509Certificates, String string)
            throws CertificateException {
        Log.debug("ClientTrustManager: checkClientTrusted(x509Certificates,"+string+") called");

180
        loadCRL();
181
        ArrayList<X509Certificate> certs = new ArrayList<>();
182 183 184 185 186 187 188 189 190
        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;

Victor Hong's avatar
Victor Hong committed
191
            List<String> peerIdentities = CertificateManager.getClientIdentities(x509Certificates[0]);
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

            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) {
260
                    Log.error(e.getMessage(), e);
261 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
                }
                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
288 289 290
                certSelector.setCertificate(x509Certificates[0]);
                PKIXBuilderParameters params = new PKIXBuilderParameters(trustStore,certSelector);
                if(useCRLs) {
291
                    params.addCertStore(crlStore);
Jay Kline's avatar
Jay Kline committed
292 293 294 295
                } else {
                    Log.debug("ClientTrustManager: no CRL's found, so setRevocationEnabled(false)");
                    params.setRevocationEnabled(false);
                }
296 297 298

                CertPathBuilderResult cpbr = cpb.build(params);
                CertPath cp = cpbr.getCertPath();
Jay Kline's avatar
Jay Kline committed
299 300 301 302 303
                if(JiveGlobals.getBooleanProperty("ocsp.enable",false)) {
                    Log.debug("ClientTrustManager: OCSP requested");
                    OCSPChecker ocspChecker = new OCSPChecker(cp,params);
                    params.addCertPathChecker(ocspChecker);
                }
304
                PKIXCertPathValidatorResult cpvResult = (PKIXCertPathValidatorResult) cpv.validate(cp, params);
305
                X509Certificate trustedCert = cpvResult.getTrustAnchor().getTrustedCert();
306 307 308 309 310 311
                if(trustedCert == null) {
                    throw new CertificateException("certificate path failed: Trusted CA is NULL");
                } else {
                    Log.debug("ClientTrustManager: Trusted CA: "+trustedCert.getSubjectDN());
                }
            }
312
            catch(CertPathBuilderException | CertPathValidatorException e) {
Jay Kline's avatar
Jay Kline committed
313
                Log.debug("ClientTrustManager:",e);
314
                throw new CertificateException("certificate path failed: "+e.getMessage());
315
            } catch(Exception e) {
Jay Kline's avatar
Jay Kline committed
316 317
                Log.debug("ClientTrustManager:",e);
                throw new CertificateException("unexpected error: "+e.getMessage());
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
            }
            
        }
    }

    /**
     * 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.
     */
343
    @Override
344 345 346 347 348 349 350
    public void checkServerTrusted(X509Certificate[] x509Certificates, String string)
            throws CertificateException {

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

    }

351
    @Override
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
    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) {
383
                Log.error(e.getMessage(), e);
384 385 386 387 388 389
                X509Certs = null;
            }
            return X509Certs;
        }
    }
}