1 /* 2 * Copyright (c) 2001, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 27 package sun.net.www.protocol.https; 28 29 import java.io.IOException; 30 import java.io.UnsupportedEncodingException; 31 import java.io.PrintStream; 32 import java.io.BufferedOutputStream; 33 import java.net.InetAddress; 34 import java.net.Socket; 35 import java.net.SocketException; 36 import java.net.URL; 37 import java.net.UnknownHostException; 38 import java.net.InetSocketAddress; 39 import java.net.Proxy; 40 import java.security.Principal; 41 import java.security.cert.*; 42 import java.util.StringTokenizer; 43 import java.util.Vector; 44 45 import javax.security.auth.x500.X500Principal; 46 47 import javax.net.ssl.*; 48 import sun.net.www.http.HttpClient; 49 import sun.net.www.protocol.http.HttpURLConnection; 50 import sun.security.action.*; 51 52 import sun.security.util.HostnameChecker; 53 import sun.security.ssl.SSLSocketImpl; 54 55 import sun.util.logging.PlatformLogger; 56 import static sun.net.www.protocol.http.HttpURLConnection.TunnelState.*; 57 58 59 /** 60 * This class provides HTTPS client URL support, building on the standard 61 * "sun.net.www" HTTP protocol handler. HTTPS is the same protocol as HTTP, 62 * but differs in the transport layer which it uses: <UL> 63 * 64 * <LI>There's a <em>Secure Sockets Layer</em> between TCP 65 * and the HTTP protocol code. 66 * 67 * <LI>It uses a different default TCP port. 68 * 69 * <LI>It doesn't use application level proxies, which can see and 70 * manipulate HTTP user level data, compromising privacy. It uses 71 * low level tunneling instead, which hides HTTP protocol and data 72 * from all third parties. (Traffic analysis is still possible). 73 * 74 * <LI>It does basic server authentication, to protect 75 * against "URL spoofing" attacks. This involves deciding 76 * whether the X.509 certificate chain identifying the server 77 * is trusted, and verifying that the name of the server is 78 * found in the certificate. (The application may enable an 79 * anonymous SSL cipher suite, and such checks are not done 80 * for anonymous ciphers.) 81 * 82 * <LI>It exposes key SSL session attributes, specifically the 83 * cipher suite in use and the server's X509 certificates, to 84 * application software which knows about this protocol handler. 85 * 86 * </UL> 87 * 88 * <P> System properties used include: <UL> 89 * 90 * <LI><em>https.proxyHost</em> ... the host supporting SSL 91 * tunneling using the conventional CONNECT syntax 92 * 93 * <LI><em>https.proxyPort</em> ... port to use on proxyHost 94 * 95 * <LI><em>https.cipherSuites</em> ... comma separated list of 96 * SSL cipher suite names to enable. 97 * 98 * <LI><em>http.nonProxyHosts</em> ... 99 * 100 * </UL> 101 * 102 * @author David Brownell 103 * @author Bill Foote 104 */ 105 106 // final for export control reasons (access to APIs); remove with care 107 final class HttpsClient extends HttpClient 108 implements HandshakeCompletedListener 109 { 110 // STATIC STATE and ACCESSORS THERETO 111 112 // HTTPS uses a different default port number than HTTP. 113 private static final int httpsPortNumber = 443; 114 115 // default HostnameVerifier class canonical name 116 private static final String defaultHVCanonicalName = 117 "javax.net.ssl.HttpsURLConnection.DefaultHostnameVerifier"; 118 119 /** Returns the default HTTPS port (443) */ 120 @Override 121 protected int getDefaultPort() { return httpsPortNumber; } 122 123 private HostnameVerifier hv; 124 private SSLSocketFactory sslSocketFactory; 125 126 // HttpClient.proxyDisabled will always be false, because we don't 127 // use an application-level HTTP proxy. We might tunnel through 128 // our http proxy, though. 129 130 131 // INSTANCE DATA 132 133 // last negotiated SSL session 134 private SSLSession session; 135 136 private String [] getCipherSuites() { 137 // 138 // If ciphers are assigned, sort them into an array. 139 // 140 String ciphers []; 141 String cipherString = 142 GetPropertyAction.getProperty("https.cipherSuites"); 143 144 if (cipherString == null || "".equals(cipherString)) { 145 ciphers = null; 146 } else { 147 StringTokenizer tokenizer; 148 Vector<String> v = new Vector<String>(); 149 150 tokenizer = new StringTokenizer(cipherString, ","); 151 while (tokenizer.hasMoreTokens()) 152 v.addElement(tokenizer.nextToken()); 153 ciphers = new String [v.size()]; 154 for (int i = 0; i < ciphers.length; i++) 155 ciphers [i] = v.elementAt(i); 156 } 157 return ciphers; 158 } 159 160 private String [] getProtocols() { 161 // 162 // If protocols are assigned, sort them into an array. 163 // 164 String protocols []; 165 String protocolString = 166 GetPropertyAction.getProperty("https.protocols"); 167 168 if (protocolString == null || "".equals(protocolString)) { 169 protocols = null; 170 } else { 171 StringTokenizer tokenizer; 172 Vector<String> v = new Vector<String>(); 173 174 tokenizer = new StringTokenizer(protocolString, ","); 175 while (tokenizer.hasMoreTokens()) 176 v.addElement(tokenizer.nextToken()); 177 protocols = new String [v.size()]; 178 for (int i = 0; i < protocols.length; i++) { 179 protocols [i] = v.elementAt(i); 180 } 181 } 182 return protocols; 183 } 184 185 private String getUserAgent() { 186 String userAgent = GetPropertyAction.getProperty("https.agent"); 187 if (userAgent == null || userAgent.length() == 0) { 188 userAgent = "JSSE"; 189 } 190 return userAgent; 191 } 192 193 // CONSTRUCTOR, FACTORY 194 195 196 /** 197 * Create an HTTPS client URL. Traffic will be tunneled through any 198 * intermediate nodes rather than proxied, so that confidentiality 199 * of data exchanged can be preserved. However, note that all the 200 * anonymous SSL flavors are subject to "person-in-the-middle" 201 * attacks against confidentiality. If you enable use of those 202 * flavors, you may be giving up the protection you get through 203 * SSL tunneling. 204 * 205 * Use New to get new HttpsClient. This constructor is meant to be 206 * used only by New method. New properly checks for URL spoofing. 207 * 208 * @param URL https URL with which a connection must be established 209 */ 210 private HttpsClient(SSLSocketFactory sf, URL url) 211 throws IOException 212 { 213 // HttpClient-level proxying is always disabled, 214 // because we override doConnect to do tunneling instead. 215 this(sf, url, (String)null, -1); 216 } 217 218 /** 219 * Create an HTTPS client URL. Traffic will be tunneled through 220 * the specified proxy server. 221 */ 222 HttpsClient(SSLSocketFactory sf, URL url, String proxyHost, int proxyPort) 223 throws IOException { 224 this(sf, url, proxyHost, proxyPort, -1); 225 } 226 227 /** 228 * Create an HTTPS client URL. Traffic will be tunneled through 229 * the specified proxy server, with a connect timeout 230 */ 231 HttpsClient(SSLSocketFactory sf, URL url, String proxyHost, int proxyPort, 232 int connectTimeout) 233 throws IOException { 234 this(sf, url, 235 (proxyHost == null? null: 236 HttpClient.newHttpProxy(proxyHost, proxyPort, "https")), 237 connectTimeout); 238 } 239 240 /** 241 * Same as previous constructor except using a Proxy 242 */ 243 HttpsClient(SSLSocketFactory sf, URL url, Proxy proxy, 244 int connectTimeout) 245 throws IOException { 246 PlatformLogger logger = HttpURLConnection.getHttpLogger(); 247 if (logger.isLoggable(PlatformLogger.Level.FINEST)) { 248 logger.finest("Creating new HttpsClient with url:" + url + " and proxy:" + proxy + 249 " with connect timeout:" + connectTimeout); 250 } 251 this.proxy = proxy; 252 setSSLSocketFactory(sf); 253 this.proxyDisabled = true; 254 255 this.host = url.getHost(); 256 this.url = url; 257 port = url.getPort(); 258 if (port == -1) { 259 port = getDefaultPort(); 260 } 261 setConnectTimeout(connectTimeout); 262 openServer(); 263 } 264 265 266 // This code largely ripped off from HttpClient.New, and 267 // it uses the same keepalive cache. 268 269 static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv, 270 HttpURLConnection httpuc) 271 throws IOException { 272 return HttpsClient.New(sf, url, hv, true, httpuc); 273 } 274 275 /** See HttpClient for the model for this method. */ 276 static HttpClient New(SSLSocketFactory sf, URL url, 277 HostnameVerifier hv, boolean useCache, 278 HttpURLConnection httpuc) throws IOException { 279 return HttpsClient.New(sf, url, hv, (String)null, -1, useCache, httpuc); 280 } 281 282 /** 283 * Get a HTTPS client to the URL. Traffic will be tunneled through 284 * the specified proxy server. 285 */ 286 static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv, 287 String proxyHost, int proxyPort, 288 HttpURLConnection httpuc) throws IOException { 289 return HttpsClient.New(sf, url, hv, proxyHost, proxyPort, true, httpuc); 290 } 291 292 static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv, 293 String proxyHost, int proxyPort, boolean useCache, 294 HttpURLConnection httpuc) 295 throws IOException { 296 return HttpsClient.New(sf, url, hv, proxyHost, proxyPort, useCache, -1, 297 httpuc); 298 } 299 300 static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv, 301 String proxyHost, int proxyPort, boolean useCache, 302 int connectTimeout, HttpURLConnection httpuc) 303 throws IOException { 304 305 return HttpsClient.New(sf, url, hv, 306 (proxyHost == null? null : 307 HttpClient.newHttpProxy(proxyHost, proxyPort, "https")), 308 useCache, connectTimeout, httpuc); 309 } 310 311 static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv, 312 Proxy p, boolean useCache, 313 int connectTimeout, HttpURLConnection httpuc) 314 throws IOException 315 { 316 if (p == null) { 317 p = Proxy.NO_PROXY; 318 } 319 PlatformLogger logger = HttpURLConnection.getHttpLogger(); 320 if (logger.isLoggable(PlatformLogger.Level.FINEST)) { 321 logger.finest("Looking for HttpClient for URL " + url + 322 " and proxy value of " + p); 323 } 324 HttpsClient ret = null; 325 if (useCache) { 326 /* see if one's already around */ 327 ret = (HttpsClient) kac.get(url, sf); 328 if (ret != null && httpuc != null && 329 httpuc.streaming() && 330 httpuc.getRequestMethod() == "POST") { 331 if (!ret.available()) 332 ret = null; 333 } 334 335 if (ret != null) { 336 if ((ret.proxy != null && ret.proxy.equals(p)) || 337 (ret.proxy == null && p == Proxy.NO_PROXY)) { 338 synchronized (ret) { 339 ret.cachedHttpClient = true; 340 assert ret.inCache; 341 ret.inCache = false; 342 if (httpuc != null && ret.needsTunneling()) 343 httpuc.setTunnelState(TUNNELING); 344 if (logger.isLoggable(PlatformLogger.Level.FINEST)) { 345 logger.finest("KeepAlive stream retrieved from the cache, " + ret); 346 } 347 } 348 } else { 349 // We cannot return this connection to the cache as it's 350 // KeepAliveTimeout will get reset. We simply close the connection. 351 // This should be fine as it is very rare that a connection 352 // to the same host will not use the same proxy. 353 synchronized(ret) { 354 if (logger.isLoggable(PlatformLogger.Level.FINEST)) { 355 logger.finest("Not returning this connection to cache: " + ret); 356 } 357 ret.inCache = false; 358 ret.closeServer(); 359 } 360 ret = null; 361 } 362 } 363 } 364 if (ret == null) { 365 ret = new HttpsClient(sf, url, p, connectTimeout); 366 } else { 367 SecurityManager security = System.getSecurityManager(); 368 if (security != null) { 369 if (ret.proxy == Proxy.NO_PROXY || ret.proxy == null) { 370 security.checkConnect(InetAddress.getByName(url.getHost()).getHostAddress(), url.getPort()); 371 } else { 372 security.checkConnect(url.getHost(), url.getPort()); 373 } 374 } 375 ret.url = url; 376 } 377 ret.setHostnameVerifier(hv); 378 379 return ret; 380 } 381 382 // METHODS 383 void setHostnameVerifier(HostnameVerifier hv) { 384 this.hv = hv; 385 } 386 387 void setSSLSocketFactory(SSLSocketFactory sf) { 388 sslSocketFactory = sf; 389 } 390 391 SSLSocketFactory getSSLSocketFactory() { 392 return sslSocketFactory; 393 } 394 395 /** 396 * The following method, createSocket, is defined in NetworkClient 397 * and overridden here so that the socket facroty is used to create 398 * new sockets. 399 */ 400 @Override 401 protected Socket createSocket() throws IOException { 402 try { 403 return sslSocketFactory.createSocket(); 404 } catch (SocketException se) { 405 // 406 // bug 6771432 407 // javax.net.SocketFactory throws a SocketException with an 408 // UnsupportedOperationException as its cause to indicate that 409 // unconnected sockets have not been implemented. 410 // 411 Throwable t = se.getCause(); 412 if (t != null && t instanceof UnsupportedOperationException) { 413 return super.createSocket(); 414 } else { 415 throw se; 416 } 417 } 418 } 419 420 421 @Override 422 public boolean needsTunneling() { 423 return (proxy != null && proxy.type() != Proxy.Type.DIRECT 424 && proxy.type() != Proxy.Type.SOCKS); 425 } 426 427 @Override 428 public void afterConnect() throws IOException, UnknownHostException { 429 if (!isCachedConnection()) { 430 SSLSocket s = null; 431 SSLSocketFactory factory = sslSocketFactory; 432 try { 433 if (!(serverSocket instanceof SSLSocket)) { 434 s = (SSLSocket)factory.createSocket(serverSocket, 435 host, port, true); 436 } else { 437 s = (SSLSocket)serverSocket; 438 if (s instanceof SSLSocketImpl) { 439 ((SSLSocketImpl)s).setHost(host); 440 } 441 } 442 } catch (IOException ex) { 443 // If we fail to connect through the tunnel, try it 444 // locally, as a last resort. If this doesn't work, 445 // throw the original exception. 446 try { 447 s = (SSLSocket)factory.createSocket(host, port); 448 } catch (IOException ignored) { 449 throw ex; 450 } 451 } 452 453 // 454 // Force handshaking, so that we get any authentication. 455 // Register a handshake callback so our session state tracks any 456 // later session renegotiations. 457 // 458 String [] protocols = getProtocols(); 459 String [] ciphers = getCipherSuites(); 460 if (protocols != null) { 461 s.setEnabledProtocols(protocols); 462 } 463 if (ciphers != null) { 464 s.setEnabledCipherSuites(ciphers); 465 } 466 s.addHandshakeCompletedListener(this); 467 468 // We have two hostname verification approaches. One is in 469 // SSL/TLS socket layer, where the algorithm is configured with 470 // SSLParameters.setEndpointIdentificationAlgorithm(), and the 471 // hostname verification is done by X509ExtendedTrustManager when 472 // the algorithm is "HTTPS". The other one is in HTTPS layer, 473 // where the algorithm is customized by 474 // HttpsURLConnection.setHostnameVerifier(), and the hostname 475 // verification is done by HostnameVerifier when the default 476 // rules for hostname verification fail. 477 // 478 // The relationship between two hostname verification approaches 479 // likes the following: 480 // 481 // | EIA algorithm 482 // +---------------------------------------------- 483 // | null | HTTPS | LDAP/other | 484 // ------------------------------------------------------------- 485 // | |1 |2 |3 | 486 // HNV | default | Set HTTPS EIA | use EIA | HTTPS | 487 // |-------------------------------------------------------- 488 // | non - |4 |5 |6 | 489 // | default | HTTPS/HNV | use EIA | HTTPS/HNV | 490 // ------------------------------------------------------------- 491 // 492 // Abbreviation: 493 // EIA: the endpoint identification algorithm in SSL/TLS 494 // socket layer 495 // HNV: the hostname verification object in HTTPS layer 496 // Notes: 497 // case 1. default HNV and EIA is null 498 // Set EIA as HTTPS, hostname check done in SSL/TLS 499 // layer. 500 // case 2. default HNV and EIA is HTTPS 501 // Use existing EIA, hostname check done in SSL/TLS 502 // layer. 503 // case 3. default HNV and EIA is other than HTTPS 504 // Use existing EIA, EIA check done in SSL/TLS 505 // layer, then do HTTPS check in HTTPS layer. 506 // case 4. non-default HNV and EIA is null 507 // No EIA, no EIA check done in SSL/TLS layer, then do 508 // HTTPS check in HTTPS layer using HNV as override. 509 // case 5. non-default HNV and EIA is HTTPS 510 // Use existing EIA, hostname check done in SSL/TLS 511 // layer. No HNV override possible. We will review this 512 // decision and may update the architecture for JDK 7. 513 // case 6. non-default HNV and EIA is other than HTTPS 514 // Use existing EIA, EIA check done in SSL/TLS layer, 515 // then do HTTPS check in HTTPS layer as override. 516 boolean needToCheckSpoofing = true; 517 String identification = 518 s.getSSLParameters().getEndpointIdentificationAlgorithm(); 519 if (identification != null && identification.length() != 0) { 520 if (identification.equalsIgnoreCase("HTTPS")) { 521 // Do not check server identity again out of SSLSocket, 522 // the endpoint will be identified during TLS handshaking 523 // in SSLSocket. 524 needToCheckSpoofing = false; 525 } // else, we don't understand the identification algorithm, 526 // need to check URL spoofing here. 527 } else { 528 boolean isDefaultHostnameVerifier = false; 529 530 // We prefer to let the SSLSocket do the spoof checks, but if 531 // the application has specified a HostnameVerifier (HNV), 532 // we will always use that. 533 if (hv != null) { 534 String canonicalName = hv.getClass().getCanonicalName(); 535 if (canonicalName != null && 536 canonicalName.equalsIgnoreCase(defaultHVCanonicalName)) { 537 isDefaultHostnameVerifier = true; 538 } 539 } else { 540 // Unlikely to happen! As the behavior is the same as the 541 // default hostname verifier, so we prefer to let the 542 // SSLSocket do the spoof checks. 543 isDefaultHostnameVerifier = true; 544 } 545 546 if (isDefaultHostnameVerifier) { 547 // If the HNV is the default from HttpsURLConnection, we 548 // will do the spoof checks in SSLSocket. 549 SSLParameters paramaters = s.getSSLParameters(); 550 paramaters.setEndpointIdentificationAlgorithm("HTTPS"); 551 s.setSSLParameters(paramaters); 552 553 needToCheckSpoofing = false; 554 } 555 } 556 557 s.startHandshake(); 558 session = s.getSession(); 559 // change the serverSocket and serverOutput 560 serverSocket = s; 561 try { 562 serverOutput = new PrintStream( 563 new BufferedOutputStream(serverSocket.getOutputStream()), 564 false, encoding); 565 } catch (UnsupportedEncodingException e) { 566 throw new InternalError(encoding+" encoding not found"); 567 } 568 569 // check URL spoofing if it has not been checked under handshaking 570 if (needToCheckSpoofing) { 571 checkURLSpoofing(hv); 572 } 573 } else { 574 // if we are reusing a cached https session, 575 // we don't need to do handshaking etc. But we do need to 576 // set the ssl session 577 session = ((SSLSocket)serverSocket).getSession(); 578 } 579 } 580 581 // Server identity checking is done according to RFC 2818: HTTP over TLS 582 // Section 3.1 Server Identity 583 private void checkURLSpoofing(HostnameVerifier hostnameVerifier) 584 throws IOException { 585 // 586 // Get authenticated server name, if any 587 // 588 String host = url.getHost(); 589 590 // if IPv6 strip off the "[]" 591 if (host != null && host.startsWith("[") && host.endsWith("]")) { 592 host = host.substring(1, host.length()-1); 593 } 594 595 Certificate[] peerCerts = null; 596 String cipher = session.getCipherSuite(); 597 try { 598 HostnameChecker checker = HostnameChecker.getInstance( 599 HostnameChecker.TYPE_TLS); 600 601 // Use ciphersuite to determine whether Kerberos is present. 602 if (cipher.startsWith("TLS_KRB5")) { 603 if (!HostnameChecker.match(host, getPeerPrincipal())) { 604 throw new SSLPeerUnverifiedException("Hostname checker" + 605 " failed for Kerberos"); 606 } 607 } else { // X.509 608 609 // get the subject's certificate 610 peerCerts = session.getPeerCertificates(); 611 612 X509Certificate peerCert; 613 if (peerCerts[0] instanceof 614 java.security.cert.X509Certificate) { 615 peerCert = (java.security.cert.X509Certificate)peerCerts[0]; 616 } else { 617 throw new SSLPeerUnverifiedException(""); 618 } 619 checker.match(host, peerCert); 620 } 621 622 // if it doesn't throw an exception, we passed. Return. 623 return; 624 625 } catch (SSLPeerUnverifiedException e) { 626 627 // 628 // client explicitly changed default policy and enabled 629 // anonymous ciphers; we can't check the standard policy 630 // 631 // ignore 632 } catch (java.security.cert.CertificateException cpe) { 633 // ignore 634 } 635 636 if ((cipher != null) && (cipher.indexOf("_anon_") != -1)) { 637 return; 638 } else if ((hostnameVerifier != null) && 639 (hostnameVerifier.verify(host, session))) { 640 return; 641 } 642 643 serverSocket.close(); 644 session.invalidate(); 645 646 throw new IOException("HTTPS hostname wrong: should be <" 647 + url.getHost() + ">"); 648 } 649 650 @Override 651 protected void putInKeepAliveCache() { 652 if (inCache) { 653 assert false : "Duplicate put to keep alive cache"; 654 return; 655 } 656 inCache = true; 657 kac.put(url, sslSocketFactory, this); 658 } 659 660 /* 661 * Close an idle connection to this URL (if it exists in the cache). 662 */ 663 @Override 664 public void closeIdleConnection() { 665 HttpClient http = kac.get(url, sslSocketFactory); 666 if (http != null) { 667 http.closeServer(); 668 } 669 } 670 671 /** 672 * Returns the cipher suite in use on this connection. 673 */ 674 String getCipherSuite() { 675 return session.getCipherSuite(); 676 } 677 678 /** 679 * Returns the certificate chain the client sent to the 680 * server, or null if the client did not authenticate. 681 */ 682 public java.security.cert.Certificate [] getLocalCertificates() { 683 return session.getLocalCertificates(); 684 } 685 686 /** 687 * Returns the certificate chain with which the server 688 * authenticated itself, or throw a SSLPeerUnverifiedException 689 * if the server did not authenticate. 690 */ 691 java.security.cert.Certificate [] getServerCertificates() 692 throws SSLPeerUnverifiedException 693 { 694 return session.getPeerCertificates(); 695 } 696 697 /** 698 * Returns the principal with which the server authenticated 699 * itself, or throw a SSLPeerUnverifiedException if the 700 * server did not authenticate. 701 */ 702 Principal getPeerPrincipal() 703 throws SSLPeerUnverifiedException 704 { 705 Principal principal; 706 try { 707 principal = session.getPeerPrincipal(); 708 } catch (AbstractMethodError e) { 709 // if the provider does not support it, fallback to peer certs. 710 // return the X500Principal of the end-entity cert. 711 java.security.cert.Certificate[] certs = 712 session.getPeerCertificates(); 713 principal = ((X509Certificate)certs[0]).getSubjectX500Principal(); 714 } 715 return principal; 716 } 717 718 /** 719 * Returns the principal the client sent to the 720 * server, or null if the client did not authenticate. 721 */ 722 Principal getLocalPrincipal() 723 { 724 Principal principal; 725 try { 726 principal = session.getLocalPrincipal(); 727 } catch (AbstractMethodError e) { 728 principal = null; 729 // if the provider does not support it, fallback to local certs. 730 // return the X500Principal of the end-entity cert. 731 java.security.cert.Certificate[] certs = 732 session.getLocalCertificates(); 733 if (certs != null) { 734 principal = ((X509Certificate)certs[0]).getSubjectX500Principal(); 735 } 736 } 737 return principal; 738 } 739 740 /** 741 * This method implements the SSL HandshakeCompleted callback, 742 * remembering the resulting session so that it may be queried 743 * for the current cipher suite and peer certificates. Servers 744 * sometimes re-initiate handshaking, so the session in use on 745 * a given connection may change. When sessions change, so may 746 * peer identities and cipher suites. 747 */ 748 public void handshakeCompleted(HandshakeCompletedEvent event) 749 { 750 session = event.getSession(); 751 } 752 753 /** 754 * @return the proxy host being used for this client, or null 755 * if we're not going through a proxy 756 */ 757 @Override 758 public String getProxyHostUsed() { 759 if (!needsTunneling()) { 760 return null; 761 } else { 762 return super.getProxyHostUsed(); 763 } 764 } 765 766 /** 767 * @return the proxy port being used for this client. Meaningless 768 * if getProxyHostUsed() gives null. 769 */ 770 @Override 771 public int getProxyPortUsed() { 772 return (proxy == null || proxy.type() == Proxy.Type.DIRECT || 773 proxy.type() == Proxy.Type.SOCKS)? -1: 774 ((InetSocketAddress)proxy.address()).getPort(); 775 } 776 }