001/*
002 * Copyright 2008-2017 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2017 Ping Identity Corporation
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.util.ssl;
022
023
024
025import java.io.File;
026import java.io.FileInputStream;
027import java.io.Serializable;
028import java.security.KeyStore;
029import java.security.cert.CertificateException;
030import java.security.cert.X509Certificate;
031import java.util.Date;
032import javax.net.ssl.TrustManager;
033import javax.net.ssl.TrustManagerFactory;
034import javax.net.ssl.X509TrustManager;
035
036import com.unboundid.util.NotMutable;
037import com.unboundid.util.ThreadSafety;
038import com.unboundid.util.ThreadSafetyLevel;
039
040import static com.unboundid.util.Debug.*;
041import static com.unboundid.util.Validator.*;
042import static com.unboundid.util.ssl.SSLMessages.*;
043
044
045
046/**
047 * This class provides an SSL trust manager that will consult a specified trust
048 * store file to determine whether to trust a certificate that is presented to
049 * it.  By default, it will use the default trust store format for the JVM
050 * (e.g., "JKS" for Sun-provided Java implementations), but alternate formats
051 * like PKCS12 may be used.
052 */
053@NotMutable()
054@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
055public final class TrustStoreTrustManager
056       implements X509TrustManager, Serializable
057{
058  /**
059   * A pre-allocated empty certificate array.
060   */
061  private static final X509Certificate[] NO_CERTIFICATES =
062       new X509Certificate[0];
063
064
065
066  /**
067   * The serial version UID for this serializable class.
068   */
069  private static final long serialVersionUID = -4093869102727719415L;
070
071
072
073  // Indicates whether to automatically trust expired or not-yet-valid
074  // certificates.
075  private final boolean examineValidityDates;
076
077  // The PIN to use to access the trust store.
078  private final char[] trustStorePIN;
079
080  // The path to the trust store file.
081  private final String trustStoreFile;
082
083  // The format to use for the trust store file.
084  private final String trustStoreFormat;
085
086
087
088  /**
089   * Creates a new instance of this trust store trust manager that will trust
090   * all certificates in the specified file within the validity window. It will
091   * use the default trust store format and will not provide a PIN when
092   * attempting to read the trust store.
093   *
094   * @param  trustStoreFile  The path to the trust store file to use.  It must
095   *                         not be {@code null}.
096   */
097  public TrustStoreTrustManager(final File trustStoreFile)
098  {
099    this(trustStoreFile.getAbsolutePath(), null, null, true);
100  }
101
102
103
104  /**
105   * Creates a new instance of this trust store trust manager that will trust
106   * all certificates in the specified file within the validity window. It will
107   * use the default trust store format and will not provide a PIN when
108   * attempting to read the trust store.
109   *
110   * @param  trustStoreFile  The path to the trust store file to use.  It must
111   *                         not be {@code null}.
112   */
113  public TrustStoreTrustManager(final String trustStoreFile)
114  {
115    this(trustStoreFile, null, null, true);
116  }
117
118
119
120  /**
121   * Creates a new instance of this trust store trust manager that will trust
122   * all certificates in the specified file with the specified constraints.
123   *
124   * @param  trustStoreFile        The path to the trust store file to use.  It
125   *                               must not be {@code null}.
126   * @param  trustStorePIN         The PIN to use to access the contents of the
127   *                               trust store.  It may be {@code null} if no
128   *                               PIN is required.
129   * @param  trustStoreFormat      The format to use for the trust store.  It
130   *                               may be {@code null} if the default format
131   *                               should be used.
132   * @param  examineValidityDates  Indicates whether to reject certificates if
133   *                               the current time is outside the validity
134   *                               window for the certificate.
135   */
136  public TrustStoreTrustManager(final File trustStoreFile,
137                                final char[] trustStorePIN,
138                                final String trustStoreFormat,
139                                final boolean examineValidityDates)
140  {
141    this(trustStoreFile.getAbsolutePath(), trustStorePIN, trustStoreFormat,
142         examineValidityDates);
143  }
144
145
146
147  /**
148   * Creates a new instance of this trust store trust manager that will trust
149   * all certificates in the specified file with the specified constraints.
150   *
151   * @param  trustStoreFile        The path to the trust store file to use.  It
152   *                               must not be {@code null}.
153   * @param  trustStorePIN         The PIN to use to access the contents of the
154   *                               trust store.  It may be {@code null} if no
155   *                               PIN is required.
156   * @param  trustStoreFormat      The format to use for the trust store.  It
157   *                               may be {@code null} if the default format
158   *                               should be used.
159   * @param  examineValidityDates  Indicates whether to reject certificates if
160   *                               the current time is outside the validity
161   *                               window for the certificate.
162   */
163  public TrustStoreTrustManager(final String trustStoreFile,
164                                final char[] trustStorePIN,
165                                final String trustStoreFormat,
166                                final boolean examineValidityDates)
167  {
168    ensureNotNull(trustStoreFile);
169
170    this.trustStoreFile       = trustStoreFile;
171    this.trustStorePIN        = trustStorePIN;
172    this.examineValidityDates = examineValidityDates;
173
174    if (trustStoreFormat == null)
175    {
176      this.trustStoreFormat = KeyStore.getDefaultType();
177    }
178    else
179    {
180      this.trustStoreFormat = trustStoreFormat;
181    }
182  }
183
184
185
186  /**
187   * Retrieves the path to the trust store file to use.
188   *
189   * @return  The path to the trust store file to use.
190   */
191  public String getTrustStoreFile()
192  {
193    return trustStoreFile;
194  }
195
196
197
198  /**
199   * Retrieves the name of the trust store file format.
200   *
201   * @return  The name of the trust store file format.
202   */
203  public String getTrustStoreFormat()
204  {
205    return trustStoreFormat;
206  }
207
208
209
210  /**
211   * Indicate whether to reject certificates if the current time is outside the
212   * validity window for the certificate.
213   *
214   * @return  {@code true} if the certificate validity time should be examined
215   *          and certificates should be rejected if they are expired or not
216   *          yet valid, or {@code false} if certificates should be accepted
217   *          even outside of the validity window.
218   */
219  public boolean examineValidityDates()
220  {
221    return examineValidityDates;
222  }
223
224
225
226  /**
227   * Retrieves a set of trust managers that may be used to determine whether the
228   * provided certificate chain should be trusted.  It will also check the
229   * validity of the provided certificates.
230   *
231   * @param  chain  The certificate chain for which to make the determination.
232   *
233   * @return  The set of trust managers that may be used to make the
234   *          determination.
235   *
236   * @throws  CertificateException  If the provided client certificate chain
237   *                                should not be trusted.
238   */
239  private synchronized X509TrustManager[] getTrustManagers(
240                                               final X509Certificate[] chain)
241          throws CertificateException
242  {
243    if (examineValidityDates)
244    {
245      final Date d = new Date();
246      for (final X509Certificate c : chain)
247      {
248        c.checkValidity(d);
249      }
250    }
251
252    final File f = new File(trustStoreFile);
253    if (! f.exists())
254    {
255      throw new CertificateException(
256           ERR_TRUSTSTORE_NO_SUCH_FILE.get(trustStoreFile));
257    }
258
259    final KeyStore ks;
260    try
261    {
262      ks = KeyStore.getInstance(trustStoreFormat);
263    }
264    catch (final Exception e)
265    {
266      debugException(e);
267
268      throw new CertificateException(
269           ERR_TRUSTSTORE_UNSUPPORTED_FORMAT.get(trustStoreFormat), e);
270    }
271
272    FileInputStream inputStream = null;
273    try
274    {
275      inputStream = new FileInputStream(f);
276      ks.load(inputStream, trustStorePIN);
277    }
278    catch (final Exception e)
279    {
280      debugException(e);
281
282      throw new CertificateException(
283           ERR_TRUSTSTORE_CANNOT_LOAD.get(trustStoreFile, trustStoreFormat,
284                                          String.valueOf(e)),
285           e);
286    }
287    finally
288    {
289      if (inputStream != null)
290      {
291        try
292        {
293          inputStream.close();
294        }
295        catch (final Exception e)
296        {
297          debugException(e);
298        }
299      }
300    }
301
302    try
303    {
304      final TrustManagerFactory factory = TrustManagerFactory.getInstance(
305           TrustManagerFactory.getDefaultAlgorithm());
306      factory.init(ks);
307      final TrustManager[] trustManagers = factory.getTrustManagers();
308      final X509TrustManager[] x509TrustManagers =
309           new X509TrustManager[trustManagers.length];
310      for (int i=0; i < trustManagers.length; i++)
311      {
312        x509TrustManagers[i] = (X509TrustManager) trustManagers[i];
313      }
314      return x509TrustManagers;
315    }
316    catch (final Exception e)
317    {
318      debugException(e);
319
320      throw new CertificateException(
321           ERR_TRUSTSTORE_CANNOT_GET_TRUST_MANAGERS.get(trustStoreFile,
322                trustStoreFormat, String.valueOf(e)),
323           e);
324    }
325  }
326
327
328
329  /**
330   * Checks to determine whether the provided client certificate chain should be
331   * trusted.
332   *
333   * @param  chain     The client certificate chain for which to make the
334   *                   determination.
335   * @param  authType  The authentication type based on the client certificate.
336   *
337   * @throws  CertificateException  If the provided client certificate chain
338   *                                should not be trusted.
339   */
340  @Override()
341  public synchronized void checkClientTrusted(final X509Certificate[] chain,
342                                final String authType)
343         throws CertificateException
344  {
345    for (final X509TrustManager m : getTrustManagers(chain))
346    {
347      m.checkClientTrusted(chain, authType);
348    }
349  }
350
351
352
353  /**
354   * Checks to determine whether the provided server certificate chain should be
355   * trusted.
356   *
357   * @param  chain     The server certificate chain for which to make the
358   *                   determination.
359   * @param  authType  The key exchange algorithm used.
360   *
361   * @throws  CertificateException  If the provided server certificate chain
362   *                                should not be trusted.
363   */
364  @Override()
365  public synchronized void checkServerTrusted(final X509Certificate[] chain,
366                                final String authType)
367         throws CertificateException
368  {
369    for (final X509TrustManager m : getTrustManagers(chain))
370    {
371      m.checkServerTrusted(chain, authType);
372    }
373  }
374
375
376
377  /**
378   * Retrieves the accepted issuer certificates for this trust manager.  This
379   * will always return an empty array.
380   *
381   * @return  The accepted issuer certificates for this trust manager.
382   */
383  @Override()
384  public synchronized X509Certificate[] getAcceptedIssuers()
385  {
386    return NO_CERTIFICATES;
387  }
388}