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