001/*
002 * Copyright 2014-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2014-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) 2014-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.net.InetAddress;
041import java.net.URI;
042import java.util.Collection;
043import java.util.List;
044import java.security.cert.Certificate;
045import java.security.cert.X509Certificate;
046import javax.net.ssl.HostnameVerifier;
047import javax.net.ssl.SSLSession;
048import javax.net.ssl.SSLSocket;
049import javax.security.auth.x500.X500Principal;
050
051import com.unboundid.asn1.ASN1OctetString;
052import com.unboundid.ldap.matchingrules.CaseIgnoreStringMatchingRule;
053import com.unboundid.ldap.sdk.DN;
054import com.unboundid.ldap.sdk.Filter;
055import com.unboundid.ldap.sdk.LDAPConnectionOptions;
056import com.unboundid.ldap.sdk.LDAPException;
057import com.unboundid.ldap.sdk.RDN;
058import com.unboundid.ldap.sdk.ResultCode;
059import com.unboundid.util.Debug;
060import com.unboundid.util.NotMutable;
061import com.unboundid.util.NotNull;
062import com.unboundid.util.Nullable;
063import com.unboundid.util.ObjectPair;
064import com.unboundid.util.StaticUtils;
065import com.unboundid.util.ThreadSafety;
066import com.unboundid.util.ThreadSafetyLevel;
067import com.unboundid.util.args.IPAddressArgumentValueValidator;
068
069import static com.unboundid.util.ssl.SSLMessages.*;
070
071
072
073/**
074 * This class provides an implementation of an {@code SSLSocket} verifier that
075 * will verify that the presented server certificate includes the address to
076 * which the client intended to establish a connection.  It will check the CN
077 * attribute of the certificate subject, as well as certain subjectAltName
078 * extensions, including dNSName, uniformResourceIdentifier, and iPAddress.
079 */
080@NotMutable()
081@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
082public final class HostNameSSLSocketVerifier
083       extends SSLSocketVerifier
084       implements HostnameVerifier
085{
086  /**
087   * The name of a system property that can be used to specify the default
088   * behavior that the verifier should exhibit when checking certificates that
089   * contain both a CN attribute in the subject DN and a subject alternative
090   * name extension that contains one or more dNSName,
091   * uniformResourceIdentifier, or iPAddress values. Although RFC 6125 section
092   * 6.4.4 indicates that the CN attribute should not be checked in certificates
093   * that have an appropriate subject alternative name extension, LDAP clients
094   * historically treat both sources as equally valid.
095   */
096  @NotNull public static final String
097       PROPERTY_CHECK_CN_WHEN_SUBJECT_ALT_NAME_IS_PRESENT =
098            HostNameSSLSocketVerifier.class.getName() +
099                 ".checkCNWhenSubjectAltNameIsPresent";
100
101
102
103  /**
104   * Indicates whether to check the CN attribute in the peer certificate's
105   * subject DN when that certificate also contains a subject alternative name
106   * extension.
107   */
108  static final boolean DEFAULT_CHECK_CN_WHEN_SUBJECT_ALT_NAME_IS_PRESENT;
109  static
110  {
111    boolean checkCN = true;
112    final String propValue = StaticUtils.getSystemProperty(
113         PROPERTY_CHECK_CN_WHEN_SUBJECT_ALT_NAME_IS_PRESENT);
114    if ((propValue != null) && propValue.equalsIgnoreCase("false"))
115    {
116      checkCN = false;
117    }
118
119    DEFAULT_CHECK_CN_WHEN_SUBJECT_ALT_NAME_IS_PRESENT = checkCN;
120  }
121
122
123
124  // Indicates whether to allow wildcard certificates which contain an asterisk
125  // as the first component of a CN subject attribute or dNSName subjectAltName
126  // extension.
127  private final boolean allowWildcards;
128
129  // Indicates whether to check the CN attribute in the peer certificate's
130  // subject DN if the certificate also contains a subject alternative name
131  // extension that contains at least dNSName, uniformResourceIdentifier, or
132  // iPAddress value.
133  private final boolean checkCNWhenSubjectAltNameIsPresent;
134
135
136
137  /**
138   * Creates a new instance of this {@code SSLSocket} verifier.
139   *
140   * @param  allowWildcards  Indicates whether to allow wildcard certificates
141   *                         that contain an asterisk in the leftmost component
142   *                         of a hostname in the dNSName or
143   *                         uniformResourceIdentifier of the subject
144   *                         alternative name extension, or in the CN attribute
145   *                         of the subject DN.
146   */
147  public HostNameSSLSocketVerifier(final boolean allowWildcards)
148  {
149    this(allowWildcards, DEFAULT_CHECK_CN_WHEN_SUBJECT_ALT_NAME_IS_PRESENT);
150  }
151
152
153
154  /**
155   * Creates a new instance of this {@code SSLSocket} verifier.
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   */
173  public HostNameSSLSocketVerifier(final boolean allowWildcards,
174              final boolean checkCNWhenSubjectAltNameIsPresent)
175  {
176    this.allowWildcards = allowWildcards;
177    this.checkCNWhenSubjectAltNameIsPresent =
178         checkCNWhenSubjectAltNameIsPresent;
179  }
180
181
182
183  /**
184   * Verifies that the provided {@code SSLSocket} is acceptable and the
185   * connection should be allowed to remain established.
186   *
187   * @param  host       The address to which the client intended the connection
188   *                    to be established.
189   * @param  port       The port to which the client intended the connection to
190   *                    be established.
191   * @param  sslSocket  The {@code SSLSocket} that should be verified.
192   *
193   * @throws  LDAPException  If a problem is identified that should prevent the
194   *                         provided {@code SSLSocket} from remaining
195   *                         established.
196   */
197  @Override()
198  public void verifySSLSocket(@NotNull final String host, final int port,
199                              @NotNull final SSLSocket sslSocket)
200         throws LDAPException
201  {
202    verifySSLSession(host, port, sslSocket.getSession());
203  }
204
205
206
207  /**
208   * Verifies that the provided {@code SSLSession} is acceptable and the
209   * connection should be allowed to remain established.
210   *
211   * @param  host        The address to which the client intended the connection
212   *                     to be established.
213   * @param  port        The port to which the client intended the connection to
214   *                     be established.
215   * @param  sslSession  The SSL session that was negotiated.
216   *
217   * @throws  LDAPException  If a problem is identified that should prevent the
218   *                         provided {@code SSLSocket} from remaining
219   *                         established.
220   */
221  private void verifySSLSession(@NotNull final String host, final int port,
222                               @NotNull final SSLSession sslSession)
223          throws LDAPException
224  {
225    try
226    {
227      // Get the certificates presented during negotiation.  The certificates
228      // will be ordered so that the server certificate comes first.
229      if (sslSession == null)
230      {
231        throw new LDAPException(ResultCode.CONNECT_ERROR,
232             ERR_HOST_NAME_SSL_SOCKET_VERIFIER_NO_SESSION.get(host, port));
233      }
234
235      final Certificate[] peerCertificateChain =
236           sslSession.getPeerCertificates();
237      if ((peerCertificateChain == null) || (peerCertificateChain.length == 0))
238      {
239        throw new LDAPException(ResultCode.CONNECT_ERROR,
240             ERR_HOST_NAME_SSL_SOCKET_VERIFIER_NO_PEER_CERTS.get(host, port));
241      }
242
243      if (peerCertificateChain[0] instanceof X509Certificate)
244      {
245        final StringBuilder certInfo = new StringBuilder();
246        if (! certificateIncludesHostname(host,
247             (X509Certificate) peerCertificateChain[0], allowWildcards,
248             checkCNWhenSubjectAltNameIsPresent, certInfo))
249        {
250          throw new LDAPException(ResultCode.CONNECT_ERROR,
251               ERR_HOST_NAME_SSL_SOCKET_VERIFIER_HOSTNAME_NOT_FOUND.get(host,
252                    certInfo.toString()));
253        }
254      }
255      else
256      {
257        throw new LDAPException(ResultCode.CONNECT_ERROR,
258             ERR_HOST_NAME_SSL_SOCKET_VERIFIER_PEER_NOT_X509.get(host, port,
259                  peerCertificateChain[0].getType()));
260      }
261    }
262    catch (final LDAPException le)
263    {
264      Debug.debugException(le);
265      throw le;
266    }
267    catch (final Exception e)
268    {
269      Debug.debugException(e);
270      throw new LDAPException(ResultCode.CONNECT_ERROR,
271           ERR_HOST_NAME_SSL_SOCKET_VERIFIER_EXCEPTION.get(host, port,
272                StaticUtils.getExceptionMessage(e)),
273           e);
274    }
275  }
276
277
278
279  /**
280   * Determines whether the provided certificate contains the specified
281   * hostname.
282   *
283   * @param  host
284   *              The address expected to be found in the provided certificate.
285   * @param  certificate
286   *              The peer certificate to be validated.
287   * @param  allowWildcards
288   *              Indicates whether to allow wildcard certificates that contain
289   *              an asterisk in the leftmost component of a hostname in the
290   *              dNSName or uniformResourceIdentifier of the subject
291   *              alternative name extension, or in the CN attribute of the
292   *              subject DN.
293   * @param  checkCNWhenSubjectAltNameIsPresent
294   *              Indicates whether to check the CN attribute in the peer
295   *              certificate's subject DN if the certificate also contains a
296   *              subject alternative name extension that contains at least one
297   *              dNSName, uniformResourceIdentifier, or iPAddress value.  RFC
298   *              6125 section 6.4.4 indicates that the CN attribute should not
299   *              be checked in certificates that have an appropriate subject
300   *              alternative name extension, although some clients may expect
301   *              CN matching anyway.
302   * @param  certInfo
303   *              A buffer into which information will be provided about the
304   *              provided certificate.
305   *
306   * @return  {@code true} if the expected hostname was found in the
307   *          certificate, or {@code false} if not.
308   */
309  static boolean certificateIncludesHostname(@NotNull final String host,
310                      @NotNull final X509Certificate certificate,
311                      final boolean allowWildcards,
312                      final boolean checkCNWhenSubjectAltNameIsPresent,
313                      @NotNull final StringBuilder certInfo)
314  {
315    // Check to see if the provided hostname is an IP address.
316    InetAddress hostInetAddress = null;
317    if (IPAddressArgumentValueValidator.isValidNumericIPAddress(host))
318    {
319      try
320      {
321        hostInetAddress =
322             LDAPConnectionOptions.DEFAULT_NAME_RESOLVER.getByName(host);
323
324        // Loopback IP addresses (but not names like "localhost") should be
325        // considered "potentially trustworthy" as per the W3C Secure Contexts
326        // Candidate Recommendation at https://www.w3.org/TR/secure-contexts/.
327        // That means that when connecting over a loopback, we can assume that
328        // the connection is established to the server we intended, even if that
329        // loopback IP address isn't in the certificate's subject alternative
330        // name extension or the CN attribute of the subject DN.
331        if (hostInetAddress.isLoopbackAddress())
332        {
333          return true;
334        }
335      }
336      catch (final Exception e)
337      {
338        Debug.debugException(e);
339      }
340    }
341
342
343    // Get the subject DN for the certificate and append it to the certInfo
344    // buffer.
345    final String subjectDNString =
346         certificate.getSubjectX500Principal().getName(X500Principal.RFC2253);
347    certInfo.append("subject='");
348    certInfo.append(subjectDNString);
349    certInfo.append('\'');
350
351
352    // Check to see if the certificate has a subject alternative name extension.
353    // If so, then check its dNSName, uniformResourceLocator, and iPAddress
354    // elements.
355    boolean hasAuthoritativeSubjectAlternativeName = false;
356    try
357    {
358      final Collection<List<?>> subjectAltNames;
359      subjectAltNames = certificate.getSubjectAlternativeNames();
360      if (subjectAltNames != null)
361      {
362        for (final List<?> l : subjectAltNames)
363        {
364          final Integer type = (Integer) l.get(0);
365          switch (type)
366          {
367            case 2: // dNSName
368              final String dnsName = (String) l.get(1);
369              certInfo.append(" dnsName='");
370              certInfo.append(dnsName);
371              certInfo.append('\'');
372
373              if (hostnameMatches(host, dnsName, allowWildcards))
374              {
375                return true;
376              }
377
378              hasAuthoritativeSubjectAlternativeName = true;
379              break;
380
381            case 6: // uniformResourceIdentifier
382              final String uriString = (String) l.get(1);
383              certInfo.append(" uniformResourceIdentifier='");
384              certInfo.append(uriString);
385              certInfo.append('\'');
386
387              final String uriHost = getHostFromURI(uriString);
388              if (uriHost != null)
389              {
390                if (IPAddressArgumentValueValidator.isValidNumericIPAddress(
391                     uriHost))
392                {
393                  if ((hostInetAddress != null) &&
394                       ipAddressMatches(hostInetAddress, uriHost))
395                  {
396                    return true;
397                  }
398                }
399                else if (hostnameMatches(host, uriHost, allowWildcards))
400                {
401                  return true;
402                }
403              }
404
405              hasAuthoritativeSubjectAlternativeName = true;
406              break;
407
408            case 7: // iPAddress
409              final String ipAddressString = (String) l.get(1);
410              certInfo.append(" ipAddress='");
411              certInfo.append(ipAddressString);
412              certInfo.append('\'');
413
414              if ((hostInetAddress != null) &&
415                   ipAddressMatches(hostInetAddress, ipAddressString))
416              {
417                return true;
418              }
419
420              hasAuthoritativeSubjectAlternativeName = true;
421              break;
422          }
423        }
424      }
425    }
426    catch (final Exception e)
427    {
428      Debug.debugException(e);
429    }
430
431
432    // If we found an authoritative subject alternative name and we should not
433    // check the subject DN to see if it contains a CN attribute, then indicate
434    // that we didn't find a match.
435    if (hasAuthoritativeSubjectAlternativeName &&
436         (! checkCNWhenSubjectAltNameIsPresent))
437    {
438      return false;
439    }
440
441
442    // Look for any CN attributes in the certificate subject.
443    try
444    {
445      final DN subjectDN = new DN(subjectDNString);
446      for (final RDN rdn : subjectDN.getRDNs())
447      {
448        final String[] names  = rdn.getAttributeNames();
449        final String[] values = rdn.getAttributeValues();
450        for (int i=0; i < names.length; i++)
451        {
452          final String lowerName = StaticUtils.toLowerCase(names[i]);
453          if (lowerName.equals("cn") || lowerName.equals("commonname") ||
454              lowerName.equals("2.5.4.3"))
455
456          {
457            final String cnValue = values[i];
458            if (IPAddressArgumentValueValidator.
459                 isValidNumericIPAddress(cnValue))
460            {
461              if ((hostInetAddress != null) &&
462                   ipAddressMatches(hostInetAddress, cnValue))
463              {
464                return true;
465              }
466            }
467            else
468            {
469              if (hostnameMatches(host, cnValue, allowWildcards))
470              {
471                return true;
472              }
473            }
474          }
475        }
476      }
477    }
478    catch (final Exception e)
479    {
480      // This shouldn't happen for a well-formed certificate subject, but we
481      // have to handle it anyway.
482      Debug.debugException(e);
483    }
484
485
486    // If we've gotten here, then we can't consider the hostname a match.
487    return false;
488  }
489
490
491
492  /**
493   * Determines whether the provided client hostname matches the given
494   * hostname from the certificate.
495   *
496   * @param  clientHostname
497   *              The hostname that the client used when establishing the
498   *              connection.
499   * @param  certificateHostname
500   *              A hostname obtained from the certificate.
501   * @param  allowWildcards
502   *              Indicates whether to allow wildcard certificates that contain
503   *              an asterisk in the leftmost component of a hostname in the
504   *              dNSName or uniformResourceIdentifier of the subject
505   *              alternative name extension, or in the CN attribute of the
506   *              subject DN.
507   *
508   * @return  {@code true} if the client hostname is considered a match for the
509   *          certificate hostname, or {@code false} if not.
510   */
511  private static boolean hostnameMatches(@NotNull final String clientHostname,
512                              @NotNull final String certificateHostname,
513                              final boolean allowWildcards)
514  {
515    // If the provided certificate hostname does not contain any asterisks,
516    // then we just need to do a case-insensitive match.
517    if (! certificateHostname.contains("*"))
518    {
519      return clientHostname.equalsIgnoreCase(certificateHostname);
520    }
521
522
523    // The certificate hostname contains at least one wildcard.  See if that's
524    // allowed.
525    if (! allowWildcards)
526    {
527      return false;
528    }
529
530
531    // Get the first component and the remainder for both the client and
532    // certificate hostnames.  If the remainder doesn't match, then it's not a
533    // match.
534    final ObjectPair<String,String> clientFirstComponentAndRemainder =
535         getFirstComponentAndRemainder(clientHostname);
536    final ObjectPair<String,String> certificateFirstComponentAndRemainder =
537         getFirstComponentAndRemainder(certificateHostname);
538    if (! clientFirstComponentAndRemainder.getSecond().equalsIgnoreCase(
539         certificateFirstComponentAndRemainder.getSecond()))
540    {
541      return false;
542    }
543
544
545    // If the first component of the certificate hostname is just an asterisk,
546    // then we can consider it a match.
547    final String certificateFirstComponent =
548         certificateFirstComponentAndRemainder.getFirst();
549    if (certificateFirstComponent.equals("*"))
550    {
551      return true;
552    }
553
554
555    // The filter has wildcard and non-wildcard components.  At this point, the
556    // easiest thing to do is to try to create a substring filter to get the
557    // individual components of the filter.
558    final Filter filter;
559    try
560    {
561      filter = Filter.create("(hostname=" + certificateFirstComponent + ')');
562      if (filter.getFilterType() != Filter.FILTER_TYPE_SUBSTRING)
563      {
564        return false;
565      }
566    }
567    catch (final Exception e)
568    {
569      Debug.debugException(e);
570      return false;
571    }
572
573
574    return CaseIgnoreStringMatchingRule.getInstance().matchesSubstring(
575         new ASN1OctetString(clientFirstComponentAndRemainder.getFirst()),
576         filter.getRawSubInitialValue(),
577         filter.getRawSubAnyValues(), filter.getRawSubFinalValue());
578  }
579
580
581
582  /**
583   * Separates the provided address into the leftmost component (everything up
584   * to the first period) and the remainder (everything else, including the
585   * first period).  If the provided address does not contain any periods, then
586   * the leftmost component will be the entire value and the remainder will be
587   * an empty string.
588   *
589   * @param  address  The address to be separated into the leftmost component
590   *                  and the remainder.  It must not be {@code null}.
591   *
592   * @return  An object pair in which the first element is the leftmost
593   *          component of the provided address and the second element is the
594   *          remainder of the address.
595   */
596  @NotNull()
597  private static ObjectPair<String,String> getFirstComponentAndRemainder(
598                                                @NotNull final String address)
599  {
600    final int periodPos = address.indexOf('.');
601    if (periodPos < 0)
602    {
603      return new ObjectPair<>(address, "");
604    }
605    else
606    {
607      return new ObjectPair<>(address.substring(0, periodPos),
608           address.substring(periodPos));
609    }
610  }
611
612
613
614  /**
615   * Determines whether the provided client IP address matches the IP address
616   * represented by the provided string.
617   *
618   * @param  clientIPAddress
619   *              The IP address that the client used when establishing the
620   *              connection.
621   * @param  certificateIPAddressString
622   *              The string representation of an IP address obtained from the
623   *              certificate.
624   *
625   * @return  {@code true} if the client hostname is considered a match for the
626   *          certificate hostname, or {@code false} if not.
627   */
628  private static boolean ipAddressMatches(
629                              @NotNull final InetAddress clientIPAddress,
630                              @NotNull final String certificateIPAddressString)
631  {
632    final InetAddress certificateIPAddress;
633    try
634    {
635      certificateIPAddress = LDAPConnectionOptions.DEFAULT_NAME_RESOLVER.
636           getByName(certificateIPAddressString);
637    }
638    catch (final Exception e)
639    {
640      Debug.debugException(e);
641      return false;
642    }
643
644    return clientIPAddress.equals(certificateIPAddress);
645  }
646
647
648
649  /**
650   * Extracts the host from the URI with the given string representation.  Note
651   * that the Java URI parser doesn't like hostnames that have wildcards, so we
652   * have to handle them specially.
653   *
654   * @param  uriString  The string representation of the URI to parse.  It must
655   *                    not be {@code null}.
656   *
657   * @return  The host extracted from the provided URI, or {@code null} if none
658   *          is available (e.g., because the URI is malformed).
659   */
660  @Nullable()
661  private static String getHostFromURI(@NotNull final String uriString)
662  {
663    final URI uri;
664    try
665    {
666      uri = new URI(uriString);
667    }
668    catch (final Exception e)
669    {
670      Debug.debugException(e);
671      return null;
672    }
673
674    final String uriHost = uri.getHost();
675    if (uriHost != null)
676    {
677      return uriHost;
678    }
679
680
681    // Java's URI code can't handle hosts with wildcards.  See if the provided
682    // URI string looks like it might contain a wildcard.  If not, then just
683    // return null.
684    if (! uriString.contains("*"))
685    {
686      return null;
687    }
688
689
690    // If Java was at least able to parse the scheme, and if the URI starts with
691    // that scheme, then we can go ahead with our own parsing attempt.
692    final String scheme = uri.getScheme();
693    if ((scheme == null) || scheme.isEmpty() ||
694         (! uriString.toLowerCase().startsWith(scheme)))
695    {
696      return null;
697    }
698
699
700    // Strip the scheme from the beginning of the URI.  Note that the scheme
701    // probably won't contain the "://", so strip that separately.
702    String paredDownURI = uriString.substring(scheme.length());
703    if (paredDownURI.startsWith("://"))
704    {
705      paredDownURI = paredDownURI.substring(3);
706    }
707
708
709    // If the pared down URI contains a slash (which would separate the hostport
710    // section from the path), then strip that off and everything after it.
711    final int slashPos = paredDownURI.indexOf('/');
712    if (slashPos >= 0)
713    {
714      paredDownURI = paredDownURI.substring(0, slashPos);
715    }
716
717
718    // If the pared down URI contains a colon (which would separate the host
719    // from the port), then strip that off and everything after it.
720    final int colonPos = paredDownURI.indexOf(':');
721    if (colonPos >= 0)
722    {
723      paredDownURI = paredDownURI.substring(0, colonPos);
724    }
725
726
727    // If there's anything left, then it should be the host.
728    if (! paredDownURI.isEmpty())
729    {
730      return paredDownURI;
731    }
732
733    return null;
734  }
735
736
737
738  /**
739   * Verifies that the provided hostname is acceptable for use with the
740   * negotiated SSL session.
741   *
742   * @param  hostname  The address to which the client intended the connection
743   *                   to be established.
744   * @param  session   The SSL session that was established.
745   */
746  @Override()
747  public boolean verify(@NotNull final String hostname,
748                        @NotNull final SSLSession session)
749  {
750    try
751    {
752      verifySSLSession(hostname, session.getPeerPort(), session);
753      return true;
754    }
755    catch (final LDAPException e)
756    {
757      Debug.debugException(e);
758      return false;
759    }
760  }
761}