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