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

Save This Page
Home » openjdk-7 » sun.net.www.protocol » https » [javadoc | source]