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