001/*
002 * Copyright 2012-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2012-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) 2012-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.security.cert.CertificateException;
041import java.security.cert.X509Certificate;
042import java.util.ArrayList;
043import java.util.Collection;
044import java.util.Collections;
045import java.util.List;
046import javax.net.ssl.X509TrustManager;
047
048import com.unboundid.util.Debug;
049import com.unboundid.util.NotMutable;
050import com.unboundid.util.NotNull;
051import com.unboundid.util.StaticUtils;
052import com.unboundid.util.ThreadSafety;
053import com.unboundid.util.ThreadSafetyLevel;
054import com.unboundid.util.Validator;
055
056import static com.unboundid.util.ssl.SSLMessages.*;
057
058
059
060/**
061 * This class provides an SSL trust manager that has the ability to delegate the
062 * determination about whether to trust a given certificate to one or more other
063 * trust managers.  It can be configured to use a logical AND (i.e., all
064 * associated trust managers must be satisfied) or a logical OR (i.e., at least
065 * one of the associated trust managers must be satisfied).
066 */
067@NotMutable()
068@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
069public final class AggregateTrustManager
070       implements X509TrustManager
071{
072  /**
073   * A pre-allocated empty certificate array.
074   */
075  @NotNull private static final X509Certificate[] NO_CERTIFICATES =
076       new X509Certificate[0];
077
078
079
080  // Indicates whether to require all of the associated trust managers to accept
081  // a presented certificate, or just to require at least one of them to accept
082  // the certificate.
083  private final boolean requireAllAccepted;
084
085  // The trust managers that will be used to ultimately make the determination.
086  @NotNull private final List<X509TrustManager> trustManagers;
087
088
089
090  /**
091   * Creates a new aggregate trust manager with the provided information.
092   *
093   * @param  requireAllAccepted  Indicates whether all of the associated trust
094   *                             managers must accept a presented certificate
095   *                             for it to be allowed, or just at least one of
096   *                             them.
097   * @param  trustManagers       The set of trust managers to use to make the
098   *                             determination.  It must not be {@code null} or
099   *                             empty.
100   */
101  public AggregateTrustManager(final boolean requireAllAccepted,
102              @NotNull final X509TrustManager ... trustManagers)
103  {
104    this(requireAllAccepted, StaticUtils.toList(trustManagers));
105  }
106
107
108
109  /**
110   * Creates a new aggregate trust manager with the provided information.
111   *
112   * @param  requireAllAccepted  Indicates whether all of the associated trust
113   *                             managers must accept a presented certificate
114   *                             for it to be allowed, or just at least one of
115   *                             them.
116   * @param  trustManagers       The set of trust managers to use to make the
117   *                             determination.  It must not be {@code null} or
118   *                             empty.
119   */
120  public AggregateTrustManager(final boolean requireAllAccepted,
121              @NotNull final Collection<X509TrustManager > trustManagers)
122  {
123    Validator.ensureNotNull(trustManagers);
124    Validator.ensureFalse(trustManagers.isEmpty(),
125         "The set of associated trust managers must not be empty.");
126
127    this.requireAllAccepted = requireAllAccepted;
128    this.trustManagers =
129         Collections.unmodifiableList(new ArrayList<>(trustManagers));
130  }
131
132
133
134  /**
135   * Indicates whether all of the associated trust managers will be required to
136   * accept a given certificate for it to be considered acceptable.
137   *
138   * @return  {@code true} if all of the associated trust managers will be
139   *          required to accept the provided certificate chain, or
140   *          {@code false} if it will be acceptable for at least one trust
141   *          manager to accept the chain even if one or more others do not.
142   */
143  public boolean requireAllAccepted()
144  {
145    return requireAllAccepted;
146  }
147
148
149
150  /**
151   * Retrieves the set of trust managers that will be used to perform the
152   * validation.
153   *
154   * @return  The set of trust managers that will be used to perform the
155   *          validation.
156   */
157  @NotNull()
158  public List<X509TrustManager> getAssociatedTrustManagers()
159  {
160    return trustManagers;
161  }
162
163
164
165  /**
166   * Checks to determine whether the provided client certificate chain should be
167   * trusted.
168   *
169   * @param  chain     The client certificate chain for which to make the
170   *                   determination.
171   * @param  authType  The authentication type based on the client certificate.
172   *
173   * @throws  CertificateException  If the provided client certificate chain
174   *                                should not be trusted.
175   */
176  @Override()
177  public void checkClientTrusted(@NotNull final X509Certificate[] chain,
178                                 @NotNull final String authType)
179         throws CertificateException
180  {
181    ArrayList<String> exceptionMessages = null;
182
183    for (final X509TrustManager m : trustManagers)
184    {
185      try
186      {
187        m.checkClientTrusted(chain, authType);
188
189        if (! requireAllAccepted)
190        {
191          return;
192        }
193      }
194      catch (final CertificateException ce)
195      {
196        Debug.debugException(ce);
197
198        if (requireAllAccepted)
199        {
200          throw ce;
201        }
202        else
203        {
204          if (exceptionMessages == null)
205          {
206            exceptionMessages = new ArrayList<>(trustManagers.size());
207          }
208
209          exceptionMessages.add(ce.getMessage());
210        }
211      }
212    }
213
214    // If we've gotten here and there are one or more exception messages, then
215    // it means that none of the associated trust managers accepted the
216    // certificate.
217    if ((exceptionMessages != null) && (! exceptionMessages.isEmpty()))
218    {
219      if (exceptionMessages.size() == 1)
220      {
221        throw new CertificateException(exceptionMessages.get(0));
222      }
223      else
224      {
225        throw new CertificateException(
226             ERR_AGGREGATE_TRUST_MANAGER_NONE_TRUSTED.get(
227                  SSLUtil.certificateToString(chain[0]),
228                  StaticUtils.concatenateStrings(exceptionMessages)));
229      }
230    }
231  }
232
233
234
235  /**
236   * Checks to determine whether the provided server certificate chain should be
237   * trusted.
238   *
239   * @param  chain     The server certificate chain for which to make the
240   *                   determination.
241   * @param  authType  The key exchange algorithm used.
242   *
243   * @throws  CertificateException  If the provided server certificate chain
244   *                                should not be trusted.
245   */
246  @Override()
247  public void checkServerTrusted(@NotNull final X509Certificate[] chain,
248                                 @NotNull final String authType)
249         throws CertificateException
250  {
251    ArrayList<String> exceptionMessages = null;
252
253    for (final X509TrustManager m : trustManagers)
254    {
255      try
256      {
257        m.checkServerTrusted(chain, authType);
258
259        if (! requireAllAccepted)
260        {
261          return;
262        }
263      }
264      catch (final CertificateException ce)
265      {
266        Debug.debugException(ce);
267
268        if (requireAllAccepted)
269        {
270          throw ce;
271        }
272        else
273        {
274          if (exceptionMessages == null)
275          {
276            exceptionMessages = new ArrayList<>(trustManagers.size());
277          }
278
279          exceptionMessages.add(ce.getMessage());
280        }
281      }
282    }
283
284    // If we've gotten here and there are one or more exception messages, then
285    // it means that none of the associated trust managers accepted the
286    // certificate.
287    if ((exceptionMessages != null) && (! exceptionMessages.isEmpty()))
288    {
289      if (exceptionMessages.size() == 1)
290      {
291        throw new CertificateException(exceptionMessages.get(0));
292      }
293      else
294      {
295        throw new CertificateException(
296             ERR_AGGREGATE_TRUST_MANAGER_NONE_TRUSTED.get(
297                  SSLUtil.certificateToString(chain[0]),
298                  StaticUtils.concatenateStrings(exceptionMessages)));
299      }
300    }
301  }
302
303
304
305  /**
306   * Retrieves the accepted issuer certificates for this trust manager.  This
307   * will always return an empty array.
308   *
309   * @return  The accepted issuer certificates for this trust manager.
310   */
311  @Override()
312  @NotNull()
313  public X509Certificate[] getAcceptedIssuers()
314  {
315    return NO_CERTIFICATES;
316  }
317}