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 }