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