001    /*
002     * Copyright 2012-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2012-2015 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     */
021    package com.unboundid.util.ssl;
022    
023    
024    
025    import java.security.cert.CertificateException;
026    import java.security.cert.X509Certificate;
027    import java.util.Collection;
028    import java.util.Collections;
029    import java.util.LinkedHashSet;
030    import java.util.Set;
031    import javax.net.ssl.X509TrustManager;
032    
033    import com.unboundid.util.NotMutable;
034    import com.unboundid.util.StaticUtils;
035    import com.unboundid.util.ThreadSafety;
036    import com.unboundid.util.ThreadSafetyLevel;
037    import com.unboundid.util.Validator;
038    
039    import 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)
064    public 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    }