001/*
002 *   Licensed to the Apache Software Foundation (ASF) under one
003 *   or more contributor license agreements.  See the NOTICE file
004 *   distributed with this work for additional information
005 *   regarding copyright ownership.  The ASF licenses this file
006 *   to you under the Apache License, Version 2.0 (the
007 *   "License"); you may not use this file except in compliance
008 *   with the License.  You may obtain a copy of the License at
009 *
010 *     https://www.apache.org/licenses/LICENSE-2.0
011 *
012 *   Unless required by applicable law or agreed to in writing,
013 *   software distributed under the License is distributed on an
014 *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *   KIND, either express or implied.  See the License for the
016 *   specific language governing permissions and limitations
017 *   under the License.
018 *
019 */
020package org.apache.directory.ldap.client.api;
021
022
023import org.apache.directory.api.i18n.I18n;
024import org.slf4j.Logger;
025import org.slf4j.LoggerFactory;
026
027import javax.net.ssl.TrustManager;
028import javax.net.ssl.TrustManagerFactory;
029import javax.net.ssl.X509TrustManager;
030
031import java.io.FileInputStream;
032import java.io.IOException;
033import java.io.InputStream;
034import java.io.Serializable;
035import java.security.KeyStore;
036import java.security.KeyStoreException;
037import java.security.NoSuchAlgorithmException;
038import java.security.cert.CertificateException;
039import java.security.cert.X509Certificate;
040import java.util.ArrayList;
041import java.util.Date;
042import java.util.List;
043
044
045/**
046 * Implement the X509TrustManager interface which will be used during JSSE truststore manager initialisation for LDAP
047 * client-to-server communications over TLS/SSL.
048 * It is used during certificate validation operations within JSSE.
049 *
050 * Note: This class allows self-signed certificates to pass the validation checks.
051 *
052 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
053 */
054public final class LdapClientTrustStoreManager implements X509TrustManager, Serializable
055{
056    /** Default serialVersionUID */
057    private static final long serialVersionUID = 1L;
058    
059    // Logging
060    private static final String CLS_NM = LdapClientTrustStoreManager.class.getName();
061    private static final Logger LOG = LoggerFactory.getLogger( CLS_NM );
062
063    // Config variables
064    private boolean isExamineValidityDates;
065    private char[] trustStorePw;
066    
067    // This is found on the classpath if trust.store.onclasspath = true (default), otherwise must include exact location on filepath:
068    private String trustStoreFile;
069    private String trustStoreFormat;
070    
071    private X509TrustManager[] x509TrustManagers;
072
073
074    /**
075     * Constructor used by connection configuration utility to load trust store manager.
076     *
077     * @param trustStoreFile    contains name of trust store file.
078     * @param trustStorePw      contains the password for trust store
079     * @param trustStoreFormat  contains the format for trust store
080     * @param isExamineValidity boolean var determines if certificate will be examined for valid dates on load.
081     */
082    public LdapClientTrustStoreManager( String trustStoreFile, char[] trustStorePw,
083        String trustStoreFormat, boolean isExamineValidity )
084    {
085        if ( trustStoreFile == null )
086        {
087            // Cannot continue, throw an unchecked exception:
088            throw new RuntimeException( I18n.err( I18n.ERR_04174_INPUT_FILE_NAME_NULL ) );
089        }
090        
091        // contains the file name of a valid JSSE TrustStore found on classpath:
092        this.trustStoreFile = trustStoreFile;
093        
094        // the password to the JSSE TrustStore:
095        this.trustStorePw = trustStorePw.clone();
096        
097        // If true, verify the current date is within the validity period for every certificate in the TrustStore:
098        this.isExamineValidityDates = isExamineValidity;
099        
100        if ( trustStoreFormat == null )
101        {
102            this.trustStoreFormat = KeyStore.getDefaultType();
103        }
104        else
105        {
106            this.trustStoreFormat = trustStoreFormat;
107        }
108    }
109
110
111    /**
112     * Determine if client certificate is to be trusted.
113     *
114     * @param x509Chain The certificate chain
115     * @param authNType The key exchange algorithm being used
116     * @throws CertificateException If the trustManager cannot be found 
117     */
118    public synchronized void checkClientTrusted( X509Certificate[] x509Chain, String authNType ) throws CertificateException
119    {
120        // For each certificate in the chain, check validity:
121        for ( X509TrustManager trustMgr : getTrustManagers( x509Chain ) )
122        {
123            trustMgr.checkClientTrusted( x509Chain, authNType );
124        }
125    }
126
127
128    /**
129     * Determine if server certificate is to be trusted.
130     *
131     * @param x509Chain The certificate chain
132     * @param authNType The key exchange algorithm being used
133     * @throws CertificateException If the trustManager cannot be found 
134     */
135    public synchronized void checkServerTrusted( X509Certificate[] x509Chain, String authNType ) throws
136        CertificateException
137    {
138        for ( X509TrustManager trustManager : getTrustManagers( x509Chain ) )
139        {
140            trustManager.checkServerTrusted( x509Chain, authNType );
141        }
142    }
143
144
145    /**
146     * Return the list of accepted issuers for this trust manager.
147     *
148     * @return array of accepted issuers
149     */
150    public synchronized X509Certificate[] getAcceptedIssuers()
151    {
152        List<X509Certificate> certificates = new ArrayList<>();
153        
154        for ( X509TrustManager trustManager : x509TrustManagers )
155        {
156            for ( X509Certificate certificate : trustManager.getAcceptedIssuers() )
157            { 
158                certificates.add( certificate );
159            }
160        }
161            
162        return certificates.toArray( new X509Certificate[]{} );
163    }
164
165
166    /**
167     * Return array of trust managers to caller.  Will verify that current date is within certs validity period.
168     *
169     * @param x509Chain contains input X.509 certificate chain.
170     * @return array of X.509 trust managers.
171     * @throws CertificateException if trustStoreFile instance variable is null.
172     */
173    private synchronized X509TrustManager[] getTrustManagers( X509Certificate[] x509Chain ) throws
174        CertificateException
175    {
176        if ( LOG.isInfoEnabled() )
177        {            
178            LOG.info( I18n.msg( I18n.MSG_04176_TRUST_MANAGER_ON_CLASSPATH, CLS_NM ) );
179        }
180        
181        return getTrustManagersOnClasspath( x509Chain );
182    }
183
184
185    /**
186     * Return array of trust managers to caller.  Will verify that current date is within certs validity period.
187     *
188     * @param x509Chain contains input X.509 certificate chain.
189     * @return array of X.509 trust managers.
190     * @throws CertificateException if trustStoreFile instance variable is null.
191     */
192    private synchronized X509TrustManager[] getTrustManagersOnClasspath( X509Certificate[] x509Chain ) throws
193        CertificateException
194    {
195        // If true, verify the current date is within each certificates validity period.
196        if ( isExamineValidityDates )
197        {
198            Date currentDate = new Date();
199            
200            for ( X509Certificate x509Cert : x509Chain )
201            {
202                x509Cert.checkValidity( currentDate );
203            }
204        }
205        
206        InputStream trustStoreInputStream;
207        
208        if ( trustStoreFile != null )
209        {
210            try
211            {
212                trustStoreInputStream = new FileInputStream( trustStoreFile );
213            }
214            catch ( IOException ioe )
215            {
216                throw new CertificateException( I18n.err( I18n.ERR_04175_TRUST_STORE_FILE_NULL ) );
217            }
218        }
219        else
220        {
221            trustStoreInputStream = getTrustStoreInputStream();
222        }
223       
224        if ( trustStoreInputStream == null )
225        {
226            throw new CertificateException( I18n.err( I18n.ERR_04176_TRUST_MANAGER_NOT_FOUND ) );
227        }
228        try
229        {
230            trustStoreInputStream.close();
231        }
232        catch ( IOException e )
233        {
234            // Eat this ioexception because it shouldn't be a problem, but log just in case:
235            LOG.warn( I18n.msg( I18n.MSG_04175_TRUST_MANAGER_IO_EXCEPTION, e.getMessage() ) );
236        }
237        
238        return loadTrustManagers( getTrustStore() );
239    }
240
241
242    /**
243     * Return an array of X.509 TrustManagers.
244     *
245     * @param trustStore handle to input trustStore
246     * @return array of trust managers
247     * @throws CertificateException if problem occurs during TrustManager initialization.
248     */
249    private X509TrustManager[] loadTrustManagers( KeyStore trustStore ) throws CertificateException
250    {
251        try
252        {
253            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( TrustManagerFactory
254                .getDefaultAlgorithm() );
255            trustManagerFactory.init( trustStore );
256            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
257            x509TrustManagers = new X509TrustManager[trustManagers.length];
258            
259            for ( int i = 0; i < trustManagers.length; i++ )
260            {
261                x509TrustManagers[i] = ( X509TrustManager ) trustManagers[i];
262            }
263        }
264        catch ( NoSuchAlgorithmException e )
265        {
266            throw new CertificateException( I18n.err( I18n.ERR_04177_NO_SUCH_ALGORITHM ), e );
267        }
268        catch ( KeyStoreException e )
269        {
270            throw new CertificateException( I18n.err( I18n.ERR_04178_CANT_LOAD_KEY_STORE ), e );
271        }
272        
273        return x509TrustManagers;
274    }
275
276
277    /**
278     * Load the TrustStore file into JSSE KeyStore instance.
279     *
280     * @return instance of JSSE KeyStore containing the LDAP Client's TrustStore file info.     *
281     * @throws CertificateException if cannot process file load.
282     */
283    private KeyStore getTrustStore() throws CertificateException
284    {
285        KeyStore trustStore;
286        
287        try
288        {
289            trustStore = KeyStore.getInstance( trustStoreFormat );
290        }
291        catch ( KeyStoreException e )
292        {
293            throw new CertificateException( I18n.err( I18n.ERR_04178_CANT_LOAD_KEY_STORE ), e );
294        }
295        
296        InputStream trustStoreInputStream = null;
297        
298        try
299        {
300            if ( trustStoreFile != null )
301            {
302                trustStoreInputStream = new FileInputStream( trustStoreFile );
303            }
304            else
305            {
306                trustStoreInputStream = getTrustStoreInputStream();
307            }
308            
309            trustStore.load( trustStoreInputStream, trustStorePw );
310        }
311        catch ( NoSuchAlgorithmException e )
312        {
313            throw new CertificateException( I18n.err( I18n.ERR_04177_NO_SUCH_ALGORITHM ), e );
314        }
315        catch ( IOException e )
316        {
317            throw new CertificateException( I18n.err( I18n.ERR_04178_CANT_LOAD_KEY_STORE ), e );
318        }
319        finally
320        {
321            // Close the input stream.
322            if ( trustStoreInputStream != null )
323            {
324                try
325                {
326                    trustStoreInputStream.close();
327                }
328                catch ( IOException e )
329                {
330                    // Eat this ioexception because it shouldn't be a problem, but log just in case:
331                    LOG.warn( I18n.err( I18n.ERR_04179_TRUST_STORE_CANT_BE_READ, e.getMessage() ) );
332                }
333            }
334        }
335        
336        return trustStore;
337    }
338
339
340    /**
341     * Read the trust store off the classpath.
342     *
343     * @return handle to inputStream containing the trust store
344     * @throws CertificateException If the file cannot be found
345     */
346    private InputStream getTrustStoreInputStream() throws CertificateException
347    {
348        ClassLoader classloader = Thread.currentThread().getContextClassLoader();
349        InputStream result = classloader.getResourceAsStream( trustStoreFile );
350        
351        if ( null == result )
352        {
353            throw new CertificateException( I18n.err( I18n.ERR_04180_FILE_DOES_NOT_EXIST_ON_CLASSPATH ) );
354        }
355        
356        return result;
357    }
358}