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