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