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