1 /* 2 * Copyright (c) 2001, 2011, 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.Socket; 34 import java.net.SocketException; 35 import java.net.URL; 36 import java.net.UnknownHostException; 37 import java.net.InetSocketAddress; 38 import java.net.Proxy; 39 import java.security.Principal; 40 import java.security.cert.*; 41 import java.util.StringTokenizer; 42 import java.util.Vector; 43 import java.security.AccessController; 44 45 import javax.security.auth.x500.X500Principal; 46 import javax.security.auth.kerberos.KerberosPrincipal; 47 48 import javax.net.ssl.*; 49 import sun.net.www.http.HttpClient; 50 import sun.security.action.*; 51 52 import sun.security.util.HostnameChecker; 53 import sun.security.ssl.SSLSocketImpl; 54 55 56 /** 57 * This class provides HTTPS client URL support, building on the standard 58 * "sun.net.www" HTTP protocol handler. HTTPS is the same protocol as HTTP, 59 * but differs in the transport layer which it uses: <UL> 60 * 61 * <LI>There's a <em>Secure Sockets Layer</em> between TCP 62 * and the HTTP protocol code. 63 * 64 * <LI>It uses a different default TCP port. 65 * 66 * <LI>It doesn't use application level proxies, which can see and 67 * manipulate HTTP user level data, compromising privacy. It uses 68 * low level tunneling instead, which hides HTTP protocol and data 69 * from all third parties. (Traffic analysis is still possible). 70 * 71 * <LI>It does basic server authentication, to protect 72 * against "URL spoofing" attacks. This involves deciding 73 * whether the X.509 certificate chain identifying the server 74 * is trusted, and verifying that the name of the server is 75 * found in the certificate. (The application may enable an 76 * anonymous SSL cipher suite, and such checks are not done 77 * for anonymous ciphers.) 78 * 79 * <LI>It exposes key SSL session attributes, specifically the 80 * cipher suite in use and the server's X509 certificates, to 81 * application software which knows about this protocol handler. 82 * 83 * </UL> 84 * 85 * <P> System properties used include: <UL> 86 * 87 * <LI><em>https.proxyHost</em> ... the host supporting SSL 88 * tunneling using the conventional CONNECT syntax 89 * 90 * <LI><em>https.proxyPort</em> ... port to use on proxyHost 91 * 92 * <LI><em>https.cipherSuites</em> ... comma separated list of 93 * SSL cipher suite names to enable. 94 * 95 * <LI><em>http.nonProxyHosts</em> ... 96 * 97 * </UL> 98 * 99 * @author David Brownell 100 * @author Bill Foote 101 */ 102 103 // final for export control reasons (access to APIs); remove with care 104 final class HttpsClient extends HttpClient 105 implements HandshakeCompletedListener 106 { 107 // STATIC STATE and ACCESSORS THERETO 108 109 // HTTPS uses a different default port number than HTTP. 110 private static final int httpsPortNumber = 443; 111 112 /** Returns the default HTTPS port (443) */ 113 @Override 114 protected int getDefaultPort() { return httpsPortNumber; } 115 116 private HostnameVerifier hv; 117 private SSLSocketFactory sslSocketFactory; 118 119 // HttpClient.proxyDisabled will always be false, because we don't 120 // use an application-level HTTP proxy. We might tunnel through 121 // our http proxy, though. 122 123 124 // INSTANCE DATA 125 126 // last negotiated SSL session 127 private SSLSession session; 128 129 private String [] getCipherSuites() { 130 // 131 // If ciphers are assigned, sort them into an array. 132 // 133 String ciphers []; 134 String cipherString = AccessController.doPrivileged( 135 new GetPropertyAction("https.cipherSuites")); 136 137 if (cipherString == null || "".equals(cipherString)) { 138 ciphers = null; 139 } else { 140 StringTokenizer tokenizer; 141 Vector<String> v = new Vector<String>(); 142 143 tokenizer = new StringTokenizer(cipherString, ","); 144 while (tokenizer.hasMoreTokens()) 145 v.addElement(tokenizer.nextToken()); 146 ciphers = new String [v.size()]; 147 for (int i = 0; i < ciphers.length; i++) 148 ciphers [i] = v.elementAt(i); 149 } 150 return ciphers; 151 } 152 153 private String [] getProtocols() { 154 // 155 // If protocols are assigned, sort them into an array. 156 // 157 String protocols []; 158 String protocolString = AccessController.doPrivileged( 159 new GetPropertyAction("https.protocols")); 160 161 if (protocolString == null || "".equals(protocolString)) { 162 protocols = null; 163 } else { 164 StringTokenizer tokenizer; 165 Vector<String> v = new Vector<String>(); 166 167 tokenizer = new StringTokenizer(protocolString, ","); 168 while (tokenizer.hasMoreTokens()) 169 v.addElement(tokenizer.nextToken()); 170 protocols = new String [v.size()]; 171 for (int i = 0; i < protocols.length; i++) { 172 protocols [i] = v.elementAt(i); 173 } 174 } 175 return protocols; 176 } 177 178 private String getUserAgent() { 179 String userAgent = java.security.AccessController.doPrivileged( 180 new sun.security.action.GetPropertyAction("https.agent")); 181 if (userAgent == null || userAgent.length() == 0) { 182 userAgent = "JSSE"; 183 } 184 return userAgent; 185 } 186 187 // should remove once HttpClient.newHttpProxy is putback 188 private static Proxy newHttpProxy(String proxyHost, int proxyPort) { 189 InetSocketAddress saddr = null; 190 final String phost = proxyHost; 191 final int pport = proxyPort < 0 ? httpsPortNumber : proxyPort; 192 try { 193 saddr = java.security.AccessController.doPrivileged(new 194 java.security.PrivilegedExceptionAction<InetSocketAddress>() { 195 public InetSocketAddress run() { 196 return new InetSocketAddress(phost, pport); 197 }}); 198 } catch (java.security.PrivilegedActionException pae) { 199 } 200 return new Proxy(Proxy.Type.HTTP, saddr); 201 } 202 203 // CONSTRUCTOR, FACTORY 204 205 206 /** 207 * Create an HTTPS client URL. Traffic will be tunneled through any 208 * intermediate nodes rather than proxied, so that confidentiality 209 * of data exchanged can be preserved. However, note that all the 210 * anonymous SSL flavors are subject to "person-in-the-middle" 211 * attacks against confidentiality. If you enable use of those 212 * flavors, you may be giving up the protection you get through 213 * SSL tunneling. 214 * 215 * Use New to get new HttpsClient. This constructor is meant to be 216 * used only by New method. New properly checks for URL spoofing. 217 * 218 * @param URL https URL with which a connection must be established 219 */ 220 private HttpsClient(SSLSocketFactory sf, URL url) 221 throws IOException 222 { 223 // HttpClient-level proxying is always disabled, 224 // because we override doConnect to do tunneling instead. 225 this(sf, url, (String)null, -1); 226 } 227 228 /** 229 * Create an HTTPS client URL. Traffic will be tunneled through 230 * the specified proxy server. 231 */ 232 HttpsClient(SSLSocketFactory sf, URL url, String proxyHost, int proxyPort) 233 throws IOException { 234 this(sf, url, proxyHost, proxyPort, -1); 235 } 236 237 /** 238 * Create an HTTPS client URL. Traffic will be tunneled through 239 * the specified proxy server, with a connect timeout 240 */ 241 HttpsClient(SSLSocketFactory sf, URL url, String proxyHost, int proxyPort, 242 int connectTimeout) 243 throws IOException { 244 this(sf, url, 245 (proxyHost == null? null: 246 HttpsClient.newHttpProxy(proxyHost, proxyPort)), 247 connectTimeout); 248 } 249 250 /** 251 * Same as previous constructor except using a Proxy 252 */ 253 HttpsClient(SSLSocketFactory sf, URL url, Proxy proxy, 254 int connectTimeout) 255 throws IOException { 256 this.proxy = proxy; 257 setSSLSocketFactory(sf); 258 this.proxyDisabled = true; 259 260 this.host = url.getHost(); 261 this.url = url; 262 port = url.getPort(); 263 if (port == -1) { 264 port = getDefaultPort(); 265 } 266 setConnectTimeout(connectTimeout); 267 openServer(); 268 } 269 270 271 // This code largely ripped off from HttpClient.New, and 272 // it uses the same keepalive cache. 273 274 static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv) 275 throws IOException { 276 return HttpsClient.New(sf, url, hv, true); 277 } 278 279 /** See HttpClient for the model for this method. */ 280 static HttpClient New(SSLSocketFactory sf, URL url, 281 HostnameVerifier hv, boolean useCache) throws IOException { 282 return HttpsClient.New(sf, url, hv, (String)null, -1, useCache); 283 } 284 285 /** 286 * Get a HTTPS client to the URL. Traffic will be tunneled through 287 * the specified proxy server. 288 */ 289 static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv, 290 String proxyHost, int proxyPort) throws IOException { 291 return HttpsClient.New(sf, url, hv, proxyHost, proxyPort, true); 292 } 293 294 static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv, 295 String proxyHost, int proxyPort, boolean useCache) 296 throws IOException { 297 return HttpsClient.New(sf, url, hv, proxyHost, proxyPort, useCache, -1); 298 } 299 300 static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv, 301 String proxyHost, int proxyPort, boolean useCache, 302 int connectTimeout) 303 throws IOException { 304 305 return HttpsClient.New(sf, url, hv, 306 (proxyHost == null? null : 307 HttpsClient.newHttpProxy(proxyHost, proxyPort)), 308 useCache, connectTimeout); 309 } 310 311 static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv, 312 Proxy p, boolean useCache, 313 int connectTimeout) 314 throws IOException { 315 HttpsClient ret = null; 316 if (useCache) { 317 /* see if one's already around */ 318 ret = (HttpsClient) kac.get(url, sf); 319 if (ret != null) { 320 ret.cachedHttpClient = true; 321 } 322 } 323 if (ret == null) { 324 ret = new HttpsClient(sf, url, p, connectTimeout); 325 } else { 326 SecurityManager security = System.getSecurityManager(); 327 if (security != null) { 328 security.checkConnect(url.getHost(), url.getPort()); 329 } 330 ret.url = url; 331 } 332 ret.setHostnameVerifier(hv); 333 334 return ret; 335 } 336 337 // METHODS 338 void setHostnameVerifier(HostnameVerifier hv) { 339 this.hv = hv; 340 } 341 342 void setSSLSocketFactory(SSLSocketFactory sf) { 343 sslSocketFactory = sf; 344 } 345 346 SSLSocketFactory getSSLSocketFactory() { 347 return sslSocketFactory; 348 } 349 350 /** 351 * The following method, createSocket, is defined in NetworkClient 352 * and overridden here so that the socket facroty is used to create 353 * new sockets. 354 */ 355 @Override 356 protected Socket createSocket() throws IOException { 357 try { 358 return sslSocketFactory.createSocket(); 359 } catch (SocketException se) { 360 // 361 // bug 6771432 362 // javax.net.SocketFactory throws a SocketException with an 363 // UnsupportedOperationException as its cause to indicate that 364 // unconnected sockets have not been implemented. 365 // 366 Throwable t = se.getCause(); 367 if (t != null && t instanceof UnsupportedOperationException) { 368 return super.createSocket(); 369 } else { 370 throw se; 371 } 372 } 373 } 374 375 376 @Override 377 public boolean needsTunneling() { 378 return (proxy != null && proxy.type() != Proxy.Type.DIRECT 379 && proxy.type() != Proxy.Type.SOCKS); 380 } 381 382 @Override 383 public void afterConnect() throws IOException, UnknownHostException { 384 if (!isCachedConnection()) { 385 SSLSocket s = null; 386 SSLSocketFactory factory = sslSocketFactory; 387 try { 388 if (!(serverSocket instanceof SSLSocket)) { 389 s = (SSLSocket)factory.createSocket(serverSocket, 390 host, port, true); 391 } else { 392 s = (SSLSocket)serverSocket; 393 if (s instanceof SSLSocketImpl) { 394 ((SSLSocketImpl)s).setHost(host); 395 } 396 } 397 } catch (IOException ex) { 398 // If we fail to connect through the tunnel, try it 399 // locally, as a last resort. If this doesn't work, 400 // throw the original exception. 401 try { 402 s = (SSLSocket)factory.createSocket(host, port); 403 } catch (IOException ignored) { 404 throw ex; 405 } 406 } 407 408 // 409 // Force handshaking, so that we get any authentication. 410 // Register a handshake callback so our session state tracks any 411 // later session renegotiations. 412 // 413 String [] protocols = getProtocols(); 414 String [] ciphers = getCipherSuites(); 415 if (protocols != null) { 416 s.setEnabledProtocols(protocols); 417 } 418 if (ciphers != null) { 419 s.setEnabledCipherSuites(ciphers); 420 } 421 s.addHandshakeCompletedListener(this); 422 423 // if the HostnameVerifier is not set, try to enable endpoint 424 // identification during handshaking 425 boolean enabledIdentification = false; 426 if (hv instanceof DefaultHostnameVerifier && 427 (s instanceof SSLSocketImpl) && 428 ((SSLSocketImpl)s).trySetHostnameVerification("HTTPS")) { 429 enabledIdentification = true; 430 } 431 432 s.startHandshake(); 433 session = s.getSession(); 434 // change the serverSocket and serverOutput 435 serverSocket = s; 436 try { 437 serverOutput = new PrintStream( 438 new BufferedOutputStream(serverSocket.getOutputStream()), 439 false, encoding); 440 } catch (UnsupportedEncodingException e) { 441 throw new InternalError(encoding+" encoding not found"); 442 } 443 444 // check URL spoofing if it has not been checked under handshaking 445 if (!enabledIdentification) { 446 checkURLSpoofing(hv); 447 } 448 } else { 449 // if we are reusing a cached https session, 450 // we don't need to do handshaking etc. But we do need to 451 // set the ssl session 452 session = ((SSLSocket)serverSocket).getSession(); 453 } 454 } 455 456 // Server identity checking is done according to RFC 2818: HTTP over TLS 457 // Section 3.1 Server Identity 458 private void checkURLSpoofing(HostnameVerifier hostnameVerifier) 459 throws IOException 460 { 461 // 462 // Get authenticated server name, if any 463 // 464 String host = url.getHost(); 465 466 // if IPv6 strip off the "[]" 467 if (host != null && host.startsWith("[") && host.endsWith("]")) { 468 host = host.substring(1, host.length()-1); 469 } 470 471 Certificate[] peerCerts = null; 472 try { 473 HostnameChecker checker = HostnameChecker.getInstance( 474 HostnameChecker.TYPE_TLS); 475 476 Principal principal = getPeerPrincipal(); 477 if (principal instanceof KerberosPrincipal) { 478 if (!HostnameChecker.match(host, (KerberosPrincipal)principal)) { 479 throw new SSLPeerUnverifiedException("Hostname checker" + 480 " failed for Kerberos"); 481 } 482 } else { 483 // get the subject's certificate 484 peerCerts = session.getPeerCertificates(); 485 486 X509Certificate peerCert; 487 if (peerCerts[0] instanceof 488 java.security.cert.X509Certificate) { 489 peerCert = (java.security.cert.X509Certificate)peerCerts[0]; 490 } else { 491 throw new SSLPeerUnverifiedException(""); 492 } 493 checker.match(host, peerCert); 494 } 495 496 // if it doesn't throw an exception, we passed. Return. 497 return; 498 499 } catch (SSLPeerUnverifiedException e) { 500 501 // 502 // client explicitly changed default policy and enabled 503 // anonymous ciphers; we can't check the standard policy 504 // 505 // ignore 506 } catch (java.security.cert.CertificateException cpe) { 507 // ignore 508 } 509 510 String cipher = session.getCipherSuite(); 511 if ((cipher != null) && (cipher.indexOf("_anon_") != -1)) { 512 return; 513 } else if ((hostnameVerifier != null) && 514 (hostnameVerifier.verify(host, session))) { 515 return; 516 } 517 518 serverSocket.close(); 519 session.invalidate(); 520 521 throw new IOException("HTTPS hostname wrong: should be <" 522 + url.getHost() + ">"); 523 } 524 525 @Override 526 protected void putInKeepAliveCache() { 527 kac.put(url, sslSocketFactory, this); 528 } 529 530 /** 531 * Returns the cipher suite in use on this connection. 532 */ 533 String getCipherSuite() { 534 return session.getCipherSuite(); 535 } 536 537 /** 538 * Returns the certificate chain the client sent to the 539 * server, or null if the client did not authenticate. 540 */ 541 public java.security.cert.Certificate [] getLocalCertificates() { 542 return session.getLocalCertificates(); 543 } 544 545 /** 546 * Returns the certificate chain with which the server 547 * authenticated itself, or throw a SSLPeerUnverifiedException 548 * if the server did not authenticate. 549 */ 550 java.security.cert.Certificate [] getServerCertificates() 551 throws SSLPeerUnverifiedException 552 { 553 return session.getPeerCertificates(); 554 } 555 556 /** 557 * Returns the X.509 certificate chain with which the server 558 * authenticated itself, or null if the server did not authenticate. 559 */ 560 javax.security.cert.X509Certificate [] getServerCertificateChain() 561 throws SSLPeerUnverifiedException 562 { 563 return session.getPeerCertificateChain(); 564 } 565 566 /** 567 * Returns the principal with which the server authenticated 568 * itself, or throw a SSLPeerUnverifiedException if the 569 * server did not authenticate. 570 */ 571 Principal getPeerPrincipal() 572 throws SSLPeerUnverifiedException 573 { 574 Principal principal; 575 try { 576 principal = session.getPeerPrincipal(); 577 } catch (AbstractMethodError e) { 578 // if the provider does not support it, fallback to peer certs. 579 // return the X500Principal of the end-entity cert. 580 java.security.cert.Certificate[] certs = 581 session.getPeerCertificates(); 582 principal = ((X509Certificate)certs[0]).getSubjectX500Principal(); 583 } 584 return principal; 585 } 586 587 /** 588 * Returns the principal the client sent to the 589 * server, or null if the client did not authenticate. 590 */ 591 Principal getLocalPrincipal() 592 { 593 Principal principal; 594 try { 595 principal = session.getLocalPrincipal(); 596 } catch (AbstractMethodError e) { 597 principal = null; 598 // if the provider does not support it, fallback to local certs. 599 // return the X500Principal of the end-entity cert. 600 java.security.cert.Certificate[] certs = 601 session.getLocalCertificates(); 602 if (certs != null) { 603 principal = ((X509Certificate)certs[0]).getSubjectX500Principal(); 604 } 605 } 606 return principal; 607 } 608 609 /** 610 * This method implements the SSL HandshakeCompleted callback, 611 * remembering the resulting session so that it may be queried 612 * for the current cipher suite and peer certificates. Servers 613 * sometimes re-initiate handshaking, so the session in use on 614 * a given connection may change. When sessions change, so may 615 * peer identities and cipher suites. 616 */ 617 @Override 618 public void handshakeCompleted(HandshakeCompletedEvent event) 619 { 620 session = event.getSession(); 621 } 622 623 /** 624 * @return the proxy host being used for this client, or null 625 * if we're not going through a proxy 626 */ 627 @Override 628 public String getProxyHostUsed() { 629 if (!needsTunneling()) { 630 return null; 631 } else { 632 return super.getProxyHostUsed(); 633 } 634 } 635 636 /** 637 * @return the proxy port being used for this client. Meaningless 638 * if getProxyHostUsed() gives null. 639 */ 640 @Override 641 public int getProxyPortUsed() { 642 return (proxy == null || proxy.type() == Proxy.Type.DIRECT || 643 proxy.type() == Proxy.Type.SOCKS)? -1: 644 ((InetSocketAddress)proxy.address()).getPort(); 645 } 646 }