001/*
002 * Copyright 2012-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2012-2017 UnboundID Corp.
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.NotMutable;
034import com.unboundid.util.StaticUtils;
035import com.unboundid.util.ThreadSafety;
036import com.unboundid.util.ThreadSafetyLevel;
037import com.unboundid.util.Validator;
038
039import static com.unboundid.util.Debug.*;
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 = Collections.unmodifiableList(
113         new ArrayList<X509TrustManager>(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  public void checkClientTrusted(final X509Certificate[] chain,
160                                 final String authType)
161         throws CertificateException
162  {
163    ArrayList<String> exceptionMessages = null;
164
165    for (final X509TrustManager m : trustManagers)
166    {
167      try
168      {
169        m.checkClientTrusted(chain, authType);
170
171        if (! requireAllAccepted)
172        {
173          return;
174        }
175      }
176      catch (final CertificateException ce)
177      {
178        debugException(ce);
179
180        if (requireAllAccepted)
181        {
182          throw ce;
183        }
184        else
185        {
186          if (exceptionMessages == null)
187          {
188            exceptionMessages = new ArrayList<String>(trustManagers.size());
189          }
190
191          exceptionMessages.add(ce.getMessage());
192        }
193      }
194    }
195
196    // If we've gotten here and there are one or more exception messages, then
197    // it means that none of the associated trust managers accepted the
198    // certificate.
199    if ((exceptionMessages != null) && (! exceptionMessages.isEmpty()))
200    {
201      if (exceptionMessages.size() == 1)
202      {
203        throw new CertificateException(exceptionMessages.get(0));
204      }
205      else
206      {
207        throw new CertificateException(
208             ERR_AGGREGATE_TRUST_MANAGER_NONE_TRUSTED.get(
209                  SSLUtil.certificateToString(chain[0]),
210                  StaticUtils.concatenateStrings(exceptionMessages)));
211      }
212    }
213  }
214
215
216
217  /**
218   * Checks to determine whether the provided server certificate chain should be
219   * trusted.
220   *
221   * @param  chain     The server certificate chain for which to make the
222   *                   determination.
223   * @param  authType  The key exchange algorithm used.
224   *
225   * @throws  CertificateException  If the provided server certificate chain
226   *                                should not be trusted.
227   */
228  public void checkServerTrusted(final X509Certificate[] chain,
229                                 final String authType)
230         throws CertificateException
231  {
232    ArrayList<String> exceptionMessages = null;
233
234    for (final X509TrustManager m : trustManagers)
235    {
236      try
237      {
238        m.checkServerTrusted(chain, authType);
239
240        if (! requireAllAccepted)
241        {
242          return;
243        }
244      }
245      catch (final CertificateException ce)
246      {
247        debugException(ce);
248
249        if (requireAllAccepted)
250        {
251          throw ce;
252        }
253        else
254        {
255          if (exceptionMessages == null)
256          {
257            exceptionMessages = new ArrayList<String>(trustManagers.size());
258          }
259
260          exceptionMessages.add(ce.getMessage());
261        }
262      }
263    }
264
265    // If we've gotten here and there are one or more exception messages, then
266    // it means that none of the associated trust managers accepted the
267    // certificate.
268    if ((exceptionMessages != null) && (! exceptionMessages.isEmpty()))
269    {
270      if (exceptionMessages.size() == 1)
271      {
272        throw new CertificateException(exceptionMessages.get(0));
273      }
274      else
275      {
276        throw new CertificateException(
277             ERR_AGGREGATE_TRUST_MANAGER_NONE_TRUSTED.get(
278                  SSLUtil.certificateToString(chain[0]),
279                  StaticUtils.concatenateStrings(exceptionMessages)));
280      }
281    }
282  }
283
284
285
286  /**
287   * Retrieves the accepted issuer certificates for this trust manager.  This
288   * will always return an empty array.
289   *
290   * @return  The accepted issuer certificates for this trust manager.
291   */
292  public X509Certificate[] getAcceptedIssuers()
293  {
294    return NO_CERTIFICATES;
295  }
296}