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