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.Collection;
028import java.util.Collections;
029import java.util.LinkedHashSet;
030import java.util.Set;
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.ssl.SSLMessages.*;
040
041
042
043/**
044 * This class provides an SSL trust manager that will only accept certificates
045 * whose hostname (as contained in the CN subject attribute or a subjectAltName
046 * extension) matches an expected value.  Only the dNSName, iPAddress, and
047 * uniformResourceIdentifier subjectAltName formats are supported.
048 * <BR><BR>
049 * This implementation optionally supports wildcard certificates, which have a
050 * hostname that starts with an asterisk followed by a period and domain or
051 * subdomain.  For example, "*.example.com" could be considered a match for
052 * anything in the "example.com" domain.  If wildcards are allowed, then only
053 * the CN subject attribute and dNSName subjectAltName extension will be
054 * examined, and only the leftmost element of a hostname may be a wildcard
055 * character.
056 * <BR><BR>
057 * Note that no other elements of the certificate are examined, so it is
058 * strongly recommended that this trust manager be used in an
059 * {@link AggregateTrustManager} in conjunction with other trust managers that
060 * perform other forms of validation.
061 */
062@NotMutable()
063@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
064public final class HostNameTrustManager
065       implements X509TrustManager
066{
067  /**
068   * A pre-allocated empty certificate array.
069   */
070  private static final X509Certificate[] NO_CERTIFICATES =
071       new X509Certificate[0];
072
073
074
075  // Indicates whether to allow wildcard certificates (which
076  private final boolean allowWildcards;
077
078  // The set of hostname values that will be considered acceptable.
079  private final Set<String> acceptableHostNames;
080
081
082
083  /**
084   * Creates a new hostname trust manager with the provided information.
085   *
086   * @param  allowWildcards       Indicates whether to allow wildcard
087   *                              certificates which contain an asterisk as the
088   *                              first component of a CN subject attribute or
089   *                              dNSName subjectAltName extension.
090   * @param  acceptableHostNames  The set of hostnames and/or IP addresses that
091   *                              will be considered acceptable.  Only
092   *                              certificates with a CN or subjectAltName value
093   *                              that exactly matches one of these names
094   *                              (ignoring differences in capitalization) will
095   *                              be considered acceptable.  It must not be
096   *                              {@code null} or empty.
097   */
098  public HostNameTrustManager(final boolean allowWildcards,
099                              final String... acceptableHostNames)
100  {
101    this(allowWildcards, StaticUtils.toList(acceptableHostNames));
102  }
103
104
105
106  /**
107   * Creates a new hostname trust manager with the provided information.
108   *
109   * @param  allowWildcards       Indicates whether to allow wildcard
110   *                              certificates which contain an asterisk as the
111   *                              first component of a CN subject attribute or
112   *                              dNSName subjectAltName extension.
113   * @param  acceptableHostNames  The set of hostnames and/or IP addresses that
114   *                              will be considered acceptable.  Only
115   *                              certificates with a CN or subjectAltName value
116   *                              that exactly matches one of these names
117   *                              (ignoring differences in capitalization) will
118   *                              be considered acceptable.  It must not be
119   *                              {@code null} or empty.
120   */
121  public HostNameTrustManager(final boolean allowWildcards,
122                              final Collection<String> acceptableHostNames)
123  {
124    Validator.ensureNotNull(acceptableHostNames);
125    Validator.ensureFalse(acceptableHostNames.isEmpty(),
126         "The set of acceptable host names must not be empty.");
127
128    this.allowWildcards = allowWildcards;
129
130    final LinkedHashSet<String> nameSet =
131         new LinkedHashSet<String>(acceptableHostNames.size());
132    for (final String s : acceptableHostNames)
133    {
134      nameSet.add(StaticUtils.toLowerCase(s));
135    }
136
137    this.acceptableHostNames = Collections.unmodifiableSet(nameSet);
138  }
139
140
141
142  /**
143   * Indicates whether wildcard certificates should be allowed, which may
144   * match multiple hosts in a given domain or subdomain.
145   *
146   * @return  {@code true} if wildcard certificates should be allowed, or
147   *          {@code false} if not.
148   */
149  public boolean allowWildcards()
150  {
151    return allowWildcards;
152  }
153
154
155
156  /**
157   * Retrieves the set of hostnames that will be considered acceptable.
158   *
159   * @return  The set of hostnames that will be considered acceptable.
160   */
161  public Set<String> getAcceptableHostNames()
162  {
163    return acceptableHostNames;
164  }
165
166
167
168  /**
169   * Checks to determine whether the provided client certificate chain should be
170   * trusted.
171   *
172   * @param  chain     The client certificate chain for which to make the
173   *                   determination.
174   * @param  authType  The authentication type based on the client certificate.
175   *
176   * @throws  CertificateException  If the provided client certificate chain
177   *                                should not be trusted.
178   */
179  public void checkClientTrusted(final X509Certificate[] chain,
180                                 final String authType)
181         throws CertificateException
182  {
183    final StringBuilder buffer = new StringBuilder();
184    for (final String s : acceptableHostNames)
185    {
186      buffer.setLength(0);
187      if (HostNameSSLSocketVerifier.certificateIncludesHostname(s, chain[0],
188           allowWildcards, buffer))
189      {
190        return;
191      }
192    }
193
194    throw new CertificateException(
195         ERR_HOSTNAME_NOT_FOUND.get(buffer.toString()));
196  }
197
198
199
200  /**
201   * Checks to determine whether the provided server certificate chain should be
202   * trusted.
203   *
204   * @param  chain     The server certificate chain for which to make the
205   *                   determination.
206   * @param  authType  The key exchange algorithm used.
207   *
208   * @throws  CertificateException  If the provided server certificate chain
209   *                                should not be trusted.
210   */
211  public void checkServerTrusted(final X509Certificate[] chain,
212                                 final String authType)
213         throws CertificateException
214  {
215    final StringBuilder buffer = new StringBuilder();
216    for (final String s : acceptableHostNames)
217    {
218      buffer.setLength(0);
219      if (HostNameSSLSocketVerifier.certificateIncludesHostname(s, chain[0],
220           allowWildcards, buffer))
221      {
222        return;
223      }
224    }
225
226    throw new CertificateException(
227         ERR_HOSTNAME_NOT_FOUND.get(buffer.toString()));
228  }
229
230
231
232  /**
233   * Retrieves the accepted issuer certificates for this trust manager.  This
234   * will always return an empty array.
235   *
236   * @return  The accepted issuer certificates for this trust manager.
237   */
238  public X509Certificate[] getAcceptedIssuers()
239  {
240    return NO_CERTIFICATES;
241  }
242}