View Javadoc
1   /*
2    *   Licensed to the Apache Software Foundation (ASF) under one
3    *   or more contributor license agreements.  See the NOTICE file
4    *   distributed with this work for additional information
5    *   regarding copyright ownership.  The ASF licenses this file
6    *   to you under the Apache License, Version 2.0 (the
7    *   "License"); you may not use this file except in compliance
8    *   with the License.  You may obtain a copy of the License at
9    *
10   *     https://www.apache.org/licenses/LICENSE-2.0
11   *
12   *   Unless required by applicable law or agreed to in writing,
13   *   software distributed under the License is distributed on an
14   *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *   KIND, either express or implied.  See the License for the
16   *   specific language governing permissions and limitations
17   *   under the License.
18   *
19   */
20  package org.apache.directory.ldap.client.api;
21  
22  
23  import org.apache.directory.api.i18n.I18n;
24  import org.slf4j.Logger;
25  import org.slf4j.LoggerFactory;
26  
27  import javax.net.ssl.TrustManager;
28  import javax.net.ssl.TrustManagerFactory;
29  import javax.net.ssl.X509TrustManager;
30  
31  import java.io.FileInputStream;
32  import java.io.IOException;
33  import java.io.InputStream;
34  import java.io.Serializable;
35  import java.security.KeyStore;
36  import java.security.KeyStoreException;
37  import java.security.NoSuchAlgorithmException;
38  import java.security.cert.CertificateException;
39  import java.security.cert.X509Certificate;
40  import java.util.ArrayList;
41  import java.util.Date;
42  import java.util.List;
43  
44  
45  /**
46   * Implement the X509TrustManager interface which will be used during JSSE truststore manager initialisation for LDAP
47   * client-to-server communications over TLS/SSL.
48   * It is used during certificate validation operations within JSSE.
49   *
50   * Note: This class allows self-signed certificates to pass the validation checks.
51   *
52   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
53   */
54  public final class LdapClientTrustStoreManager implements X509TrustManager, Serializable
55  {
56      /** Default serialVersionUID */
57      private static final long serialVersionUID = 1L;
58      
59      // Logging
60      private static final String CLS_NM = LdapClientTrustStoreManager.class.getName();
61      private static final Logger LOG = LoggerFactory.getLogger( CLS_NM );
62  
63      // Config variables
64      private boolean isExamineValidityDates;
65      private char[] trustStorePw;
66      
67      // This is found on the classpath if trust.store.onclasspath = true (default), otherwise must include exact location on filepath:
68      private String trustStoreFile;
69      private String trustStoreFormat;
70      
71      private X509TrustManager[] x509TrustManagers;
72  
73  
74      /**
75       * Constructor used by connection configuration utility to load trust store manager.
76       *
77       * @param trustStoreFile    contains name of trust store file.
78       * @param trustStorePw      contains the password for trust store
79       * @param trustStoreFormat  contains the format for trust store
80       * @param isExamineValidity boolean var determines if certificate will be examined for valid dates on load.
81       */
82      public LdapClientTrustStoreManager( String trustStoreFile, char[] trustStorePw,
83          String trustStoreFormat, boolean isExamineValidity )
84      {
85          if ( trustStoreFile == null )
86          {
87              // Cannot continue, throw an unchecked exception:
88              throw new RuntimeException( I18n.err( I18n.ERR_04174_INPUT_FILE_NAME_NULL ) );
89          }
90          
91          // contains the file name of a valid JSSE TrustStore found on classpath:
92          this.trustStoreFile = trustStoreFile;
93          
94          // the password to the JSSE TrustStore:
95          this.trustStorePw = trustStorePw.clone();
96          
97          // If true, verify the current date is within the validity period for every certificate in the TrustStore:
98          this.isExamineValidityDates = isExamineValidity;
99          
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 }