001/*
002 * Copyright 2014-2022 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2014-2022 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-2022 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 subject
106   * alternative name 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    // Check to see if the certificate has a subject alternative name extension.
344    // If so, then check its dNSName, uniformResourceLocator, and iPAddress
345    // elements.
346    boolean hasAuthoritativeSubjectAlternativeName = false;
347    try
348    {
349      final Collection<List<?>> subjectAltNames;
350      subjectAltNames = certificate.getSubjectAlternativeNames();
351      if (subjectAltNames != null)
352      {
353        for (final List<?> l : subjectAltNames)
354        {
355          final Integer type = (Integer) l.get(0);
356          switch (type)
357          {
358            case 2: // dNSName
359              final String dnsName = (String) l.get(1);
360              certInfo.append(" dNSName='");
361              certInfo.append(dnsName);
362              certInfo.append('\'');
363
364              if (hostnameMatches(host, dnsName, allowWildcards))
365              {
366                return true;
367              }
368
369              hasAuthoritativeSubjectAlternativeName = true;
370              break;
371
372            case 6: // uniformResourceIdentifier
373              final String uriString = (String) l.get(1);
374              certInfo.append(" uniformResourceIdentifier='");
375              certInfo.append(uriString);
376              certInfo.append('\'');
377
378              final String uriHost = getHostFromURI(uriString);
379              if (uriHost != null)
380              {
381                if (IPAddressArgumentValueValidator.isValidNumericIPAddress(
382                     uriHost))
383                {
384                  if ((hostInetAddress != null) &&
385                       ipAddressMatches(hostInetAddress, uriHost))
386                  {
387                    return true;
388                  }
389                }
390                else if (hostnameMatches(host, uriHost, allowWildcards))
391                {
392                  return true;
393                }
394              }
395
396              hasAuthoritativeSubjectAlternativeName = true;
397              break;
398
399            case 7: // iPAddress
400              final String ipAddressString = (String) l.get(1);
401              certInfo.append(" iPAddress='");
402              certInfo.append(ipAddressString);
403              certInfo.append('\'');
404
405              if ((hostInetAddress != null) &&
406                   ipAddressMatches(hostInetAddress, ipAddressString))
407              {
408                return true;
409              }
410
411              hasAuthoritativeSubjectAlternativeName = true;
412              break;
413          }
414        }
415      }
416    }
417    catch (final Exception e)
418    {
419      Debug.debugException(e);
420    }
421
422
423    // If we found an authoritative subject alternative name and we should not
424    // check the subject DN to see if it contains a CN attribute, then indicate
425    // that we didn't find a match.
426    if (hasAuthoritativeSubjectAlternativeName &&
427         (! checkCNWhenSubjectAltNameIsPresent))
428    {
429      return false;
430    }
431
432
433    // Look for any CN attributes in the certificate subject.
434    final String subjectDNString =
435         certificate.getSubjectX500Principal().getName(X500Principal.RFC2253);
436    certInfo.append("subject='");
437    certInfo.append(subjectDNString);
438    certInfo.append('\'');
439
440    try
441    {
442      final DN subjectDN = new DN(subjectDNString);
443      for (final RDN rdn : subjectDN.getRDNs())
444      {
445        final String[] names  = rdn.getAttributeNames();
446        final String[] values = rdn.getAttributeValues();
447        for (int i=0; i < names.length; i++)
448        {
449          final String lowerName = StaticUtils.toLowerCase(names[i]);
450          if (lowerName.equals("cn") || lowerName.equals("commonname") ||
451              lowerName.equals("2.5.4.3"))
452
453          {
454            final String cnValue = values[i];
455            if (IPAddressArgumentValueValidator.
456                 isValidNumericIPAddress(cnValue))
457            {
458              if ((hostInetAddress != null) &&
459                   ipAddressMatches(hostInetAddress, cnValue))
460              {
461                return true;
462              }
463            }
464            else
465            {
466              if (hostnameMatches(host, cnValue, allowWildcards))
467              {
468                return true;
469              }
470            }
471          }
472        }
473      }
474    }
475    catch (final Exception e)
476    {
477      // This shouldn't happen for a well-formed certificate subject, but we
478      // have to handle it anyway.
479      Debug.debugException(e);
480    }
481
482
483    // If we've gotten here, then we can't consider the hostname a match.
484    return false;
485  }
486
487
488
489  /**
490   * Determines whether the provided client hostname matches the given
491   * hostname from the certificate.
492   *
493   * @param  clientHostname
494   *              The hostname that the client used when establishing the
495   *              connection.
496   * @param  certificateHostname
497   *              A hostname obtained from the certificate.
498   * @param  allowWildcards
499   *              Indicates whether to allow wildcard certificates that contain
500   *              an asterisk in the leftmost component of a hostname in the
501   *              dNSName or uniformResourceIdentifier of the subject
502   *              alternative name extension, or in the CN attribute of the
503   *              subject DN.
504   *
505   * @return  {@code true} if the client hostname is considered a match for the
506   *          certificate hostname, or {@code false} if not.
507   */
508  private static boolean hostnameMatches(@NotNull final String clientHostname,
509                              @NotNull final String certificateHostname,
510                              final boolean allowWildcards)
511  {
512    // If the provided certificate hostname does not contain any asterisks,
513    // then we just need to do a case-insensitive match.
514    if (! certificateHostname.contains("*"))
515    {
516      return clientHostname.equalsIgnoreCase(certificateHostname);
517    }
518
519
520    // The certificate hostname contains at least one wildcard.  See if that's
521    // allowed.
522    if (! allowWildcards)
523    {
524      return false;
525    }
526
527
528    // Get the first component and the remainder for both the client and
529    // certificate hostnames.  If the remainder doesn't match, then it's not a
530    // match.
531    final ObjectPair<String,String> clientFirstComponentAndRemainder =
532         getFirstComponentAndRemainder(clientHostname);
533    final ObjectPair<String,String> certificateFirstComponentAndRemainder =
534         getFirstComponentAndRemainder(certificateHostname);
535    if (! clientFirstComponentAndRemainder.getSecond().equalsIgnoreCase(
536         certificateFirstComponentAndRemainder.getSecond()))
537    {
538      return false;
539    }
540
541
542    // If the first component of the certificate hostname is just an asterisk,
543    // then we can consider it a match.
544    final String certificateFirstComponent =
545         certificateFirstComponentAndRemainder.getFirst();
546    if (certificateFirstComponent.equals("*"))
547    {
548      return true;
549    }
550
551
552    // The filter has wildcard and non-wildcard components.  At this point, the
553    // easiest thing to do is to try to create a substring filter to get the
554    // individual components of the filter.
555    final Filter filter;
556    try
557    {
558      filter = Filter.create("(hostname=" + certificateFirstComponent + ')');
559      if (filter.getFilterType() != Filter.FILTER_TYPE_SUBSTRING)
560      {
561        return false;
562      }
563    }
564    catch (final Exception e)
565    {
566      Debug.debugException(e);
567      return false;
568    }
569
570
571    return CaseIgnoreStringMatchingRule.getInstance().matchesSubstring(
572         new ASN1OctetString(clientFirstComponentAndRemainder.getFirst()),
573         filter.getRawSubInitialValue(),
574         filter.getRawSubAnyValues(), filter.getRawSubFinalValue());
575  }
576
577
578
579  /**
580   * Separates the provided address into the leftmost component (everything up
581   * to the first period) and the remainder (everything else, including the
582   * first period).  If the provided address does not contain any periods, then
583   * the leftmost component will be the entire value and the remainder will be
584   * an empty string.
585   *
586   * @param  address  The address to be separated into the leftmost component
587   *                  and the remainder.  It must not be {@code null}.
588   *
589   * @return  An object pair in which the first element is the leftmost
590   *          component of the provided address and the second element is the
591   *          remainder of the address.
592   */
593  @NotNull()
594  private static ObjectPair<String,String> getFirstComponentAndRemainder(
595                                                @NotNull final String address)
596  {
597    final int periodPos = address.indexOf('.');
598    if (periodPos < 0)
599    {
600      return new ObjectPair<>(address, "");
601    }
602    else
603    {
604      return new ObjectPair<>(address.substring(0, periodPos),
605           address.substring(periodPos));
606    }
607  }
608
609
610
611  /**
612   * Determines whether the provided client IP address matches the IP address
613   * represented by the provided string.
614   *
615   * @param  clientIPAddress
616   *              The IP address that the client used when establishing the
617   *              connection.
618   * @param  certificateIPAddressString
619   *              The string representation of an IP address obtained from the
620   *              certificate.
621   *
622   * @return  {@code true} if the client hostname is considered a match for the
623   *          certificate hostname, or {@code false} if not.
624   */
625  private static boolean ipAddressMatches(
626                              @NotNull final InetAddress clientIPAddress,
627                              @NotNull final String certificateIPAddressString)
628  {
629    final InetAddress certificateIPAddress;
630    try
631    {
632      certificateIPAddress = LDAPConnectionOptions.DEFAULT_NAME_RESOLVER.
633           getByName(certificateIPAddressString);
634    }
635    catch (final Exception e)
636    {
637      Debug.debugException(e);
638      return false;
639    }
640
641    return clientIPAddress.equals(certificateIPAddress);
642  }
643
644
645
646  /**
647   * Extracts the host from the URI with the given string representation.  Note
648   * that the Java URI parser doesn't like hostnames that have wildcards, so we
649   * have to handle them specially.
650   *
651   * @param  uriString  The string representation of the URI to parse.  It must
652   *                    not be {@code null}.
653   *
654   * @return  The host extracted from the provided URI, or {@code null} if none
655   *          is available (e.g., because the URI is malformed).
656   */
657  @Nullable()
658  private static String getHostFromURI(@NotNull final String uriString)
659  {
660    final URI uri;
661    try
662    {
663      uri = new URI(uriString);
664    }
665    catch (final Exception e)
666    {
667      Debug.debugException(e);
668      return null;
669    }
670
671    final String uriHost = uri.getHost();
672    if (uriHost != null)
673    {
674      return uriHost;
675    }
676
677
678    // Java's URI code can't handle hosts with wildcards.  See if the provided
679    // URI string looks like it might contain a wildcard.  If not, then just
680    // return null.
681    if (! uriString.contains("*"))
682    {
683      return null;
684    }
685
686
687    // If Java was at least able to parse the scheme, and if the URI starts with
688    // that scheme, then we can go ahead with our own parsing attempt.
689    final String scheme = uri.getScheme();
690    if ((scheme == null) || scheme.isEmpty() ||
691         (! uriString.toLowerCase().startsWith(scheme)))
692    {
693      return null;
694    }
695
696
697    // Strip the scheme from the beginning of the URI.  Note that the scheme
698    // probably won't contain the "://", so strip that separately.
699    String paredDownURI = uriString.substring(scheme.length());
700    if (paredDownURI.startsWith("://"))
701    {
702      paredDownURI = paredDownURI.substring(3);
703    }
704
705
706    // If the pared down URI contains a slash (which would separate the hostport
707    // section from the path), then strip that off and everything after it.
708    final int slashPos = paredDownURI.indexOf('/');
709    if (slashPos >= 0)
710    {
711      paredDownURI = paredDownURI.substring(0, slashPos);
712    }
713
714
715    // If the pared down URI contains a colon (which would separate the host
716    // from the port), then strip that off and everything after it.
717    final int colonPos = paredDownURI.indexOf(':');
718    if (colonPos >= 0)
719    {
720      paredDownURI = paredDownURI.substring(0, colonPos);
721    }
722
723
724    // If there's anything left, then it should be the host.
725    if (! paredDownURI.isEmpty())
726    {
727      return paredDownURI;
728    }
729
730    return null;
731  }
732
733
734
735  /**
736   * Verifies that the provided hostname is acceptable for use with the
737   * negotiated SSL session.
738   *
739   * @param  hostname  The address to which the client intended the connection
740   *                   to be established.
741   * @param  session   The SSL session that was established.
742   */
743  @Override()
744  public boolean verify(@NotNull final String hostname,
745                        @NotNull final SSLSession session)
746  {
747    try
748    {
749      verifySSLSession(hostname, session.getPeerPort(), session);
750      return true;
751    }
752    catch (final LDAPException e)
753    {
754      Debug.debugException(e);
755      return false;
756    }
757  }
758}