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