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}