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.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.CryptoHelper;
052import com.unboundid.util.Debug;
053import com.unboundid.util.NotMutable;
054import com.unboundid.util.NotNull;
055import com.unboundid.util.Nullable;
056import com.unboundid.util.StaticUtils;
057import com.unboundid.util.ThreadSafety;
058import com.unboundid.util.ThreadSafetyLevel;
059import com.unboundid.util.Validator;
060
061import static com.unboundid.util.ssl.SSLMessages.*;
062
063
064
065/**
066 * This class provides an SSL trust manager that will consult a specified trust
067 * store file to determine whether to trust a certificate that is presented to
068 * it.  By default, it will use the default trust store format for the JVM
069 * (e.g., "JKS" for Sun-provided Java implementations), but alternate formats
070 * like PKCS12 may be used.
071 */
072@NotMutable()
073@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
074public final class TrustStoreTrustManager
075       implements X509TrustManager, Serializable
076{
077  /**
078   * A pre-allocated empty certificate array.
079   */
080  @NotNull private static final X509Certificate[] NO_CERTIFICATES =
081       new X509Certificate[0];
082
083
084
085  /**
086   * The serial version UID for this serializable class.
087   */
088  private static final long serialVersionUID = -4093869102727719415L;
089
090
091
092  // Indicates whether to automatically trust expired or not-yet-valid
093  // certificates.
094  private final boolean examineValidityDates;
095
096  // The PIN to use to access the trust store.
097  @Nullable private final char[] trustStorePIN;
098
099  // The path to the trust store file.
100  @NotNull private final String trustStoreFile;
101
102  // The format to use for the trust store file.
103  @NotNull private final String trustStoreFormat;
104
105
106
107  /**
108   * Creates a new instance of this trust store trust manager that will trust
109   * all certificates in the specified file within the validity window. It will
110   * use the default trust store format and will not provide a PIN when
111   * attempting to read the trust store.
112   *
113   * @param  trustStoreFile  The path to the trust store file to use.  It must
114   *                         not be {@code null}.
115   */
116  public TrustStoreTrustManager(@NotNull final File trustStoreFile)
117  {
118    this(trustStoreFile.getAbsolutePath(), null, null, true);
119  }
120
121
122
123  /**
124   * Creates a new instance of this trust store trust manager that will trust
125   * all certificates in the specified file within the validity window. It will
126   * use the default trust store format and will not provide a PIN when
127   * attempting to read the trust store.
128   *
129   * @param  trustStoreFile  The path to the trust store file to use.  It must
130   *                         not be {@code null}.
131   */
132  public TrustStoreTrustManager(@NotNull final String trustStoreFile)
133  {
134    this(trustStoreFile, null, null, true);
135  }
136
137
138
139  /**
140   * Creates a new instance of this trust store trust manager that will trust
141   * all certificates in the specified file with the specified constraints.
142   *
143   * @param  trustStoreFile        The path to the trust store file to use.  It
144   *                               must not be {@code null}.
145   * @param  trustStorePIN         The PIN to use to access the contents of the
146   *                               trust store.  It may be {@code null} if no
147   *                               PIN is required.
148   * @param  trustStoreFormat      The format to use for the trust store.  It
149   *                               may be {@code null} if the default format
150   *                               should be used.
151   * @param  examineValidityDates  Indicates whether to reject certificates if
152   *                               the current time is outside the validity
153   *                               window for the certificate.
154   */
155  public TrustStoreTrustManager(@NotNull final File trustStoreFile,
156                                @Nullable final char[] trustStorePIN,
157                                @Nullable final String trustStoreFormat,
158                                final boolean examineValidityDates)
159  {
160    this(trustStoreFile.getAbsolutePath(), trustStorePIN, trustStoreFormat,
161         examineValidityDates);
162  }
163
164
165
166  /**
167   * Creates a new instance of this trust store trust manager that will trust
168   * all certificates in the specified file with the specified constraints.
169   *
170   * @param  trustStoreFile        The path to the trust store file to use.  It
171   *                               must not be {@code null}.
172   * @param  trustStorePIN         The PIN to use to access the contents of the
173   *                               trust store.  It may be {@code null} if no
174   *                               PIN is required.
175   * @param  trustStoreFormat      The format to use for the trust store.  It
176   *                               may be {@code null} if the default format
177   *                               should be used.
178   * @param  examineValidityDates  Indicates whether to reject certificates if
179   *                               the current time is outside the validity
180   *                               window for the certificate.
181   */
182  public TrustStoreTrustManager(@NotNull final String trustStoreFile,
183                                @Nullable final char[] trustStorePIN,
184                                @Nullable final String trustStoreFormat,
185                                final boolean examineValidityDates)
186  {
187    Validator.ensureNotNull(trustStoreFile);
188
189    this.trustStoreFile       = trustStoreFile;
190    this.trustStorePIN        = trustStorePIN;
191    this.examineValidityDates = examineValidityDates;
192
193    if (trustStoreFormat == null)
194    {
195      this.trustStoreFormat = CryptoHelper.getDefaultKeyStoreType();
196    }
197    else
198    {
199      this.trustStoreFormat = trustStoreFormat;
200    }
201  }
202
203
204
205  /**
206   * Retrieves the path to the trust store file to use.
207   *
208   * @return  The path to the trust store file to use.
209   */
210  @NotNull()
211  public String getTrustStoreFile()
212  {
213    return trustStoreFile;
214  }
215
216
217
218  /**
219   * Retrieves the name of the trust store file format.
220   *
221   * @return  The name of the trust store file format.
222   */
223  @NotNull()
224  public String getTrustStoreFormat()
225  {
226    return trustStoreFormat;
227  }
228
229
230
231  /**
232   * Indicate whether to reject certificates if the current time is outside the
233   * validity window for the certificate.
234   *
235   * @return  {@code true} if the certificate validity time should be examined
236   *          and certificates should be rejected if they are expired or not
237   *          yet valid, or {@code false} if certificates should be accepted
238   *          even outside of the validity window.
239   */
240  public boolean examineValidityDates()
241  {
242    return examineValidityDates;
243  }
244
245
246
247  /**
248   * Retrieves a set of trust managers that may be used to determine whether the
249   * provided certificate chain should be trusted.  It will also check the
250   * validity of the provided certificates.
251   *
252   * @param  chain  The certificate chain for which to make the determination.
253   *
254   * @return  The set of trust managers that may be used to make the
255   *          determination.
256   *
257   * @throws  CertificateException  If the provided client certificate chain
258   *                                should not be trusted.
259   */
260  @NotNull()
261  private X509TrustManager[] getTrustManagers(
262                                  @NotNull final X509Certificate[] chain)
263          throws CertificateException
264  {
265    if (examineValidityDates)
266    {
267      final Date d = new Date();
268      for (final X509Certificate c : chain)
269      {
270        c.checkValidity(d);
271      }
272    }
273
274    final File f = new File(trustStoreFile);
275    if (! f.exists())
276    {
277      throw new CertificateException(
278           ERR_TRUSTSTORE_NO_SUCH_FILE.get(trustStoreFile));
279    }
280
281    final KeyStore ks;
282    try
283    {
284      ks = CryptoHelper.getKeyStore(trustStoreFormat);
285    }
286    catch (final Exception e)
287    {
288      Debug.debugException(e);
289
290      throw new CertificateException(
291           ERR_TRUSTSTORE_UNSUPPORTED_FORMAT.get(trustStoreFormat), e);
292    }
293
294    try (FileInputStream inputStream = new FileInputStream(f))
295    {
296      ks.load(inputStream, trustStorePIN);
297    }
298    catch (final Exception e)
299    {
300      Debug.debugException(e);
301
302      throw new CertificateException(
303           ERR_TRUSTSTORE_CANNOT_LOAD.get(trustStoreFile, trustStoreFormat,
304                StaticUtils.getExceptionMessage(e)),
305           e);
306    }
307
308    try
309    {
310      final TrustManagerFactory factory = CryptoHelper.getTrustManagerFactory();
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}