blob: 7f82e993161999189b40b1f600d236e351086494 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2001-2007 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
27package sun.net.www.protocol.https;
28
29import java.io.IOException;
30import java.io.UnsupportedEncodingException;
31import java.io.InputStream;
32import java.io.OutputStream;
33import java.io.FileInputStream;
34import java.io.PrintStream;
35import java.io.BufferedOutputStream;
36import java.net.Socket;
37import java.net.URL;
38import java.net.UnknownHostException;
39import java.net.InetAddress;
40import java.net.InetSocketAddress;
41import java.net.Proxy;
42import java.net.CookieHandler;
43import java.net.Authenticator;
44import java.net.PasswordAuthentication;
45import java.security.Principal;
46import java.security.KeyStore;
47import java.security.PrivateKey;
48import java.security.cert.*;
49import java.util.StringTokenizer;
50import java.util.Vector;
51import java.util.Collection;
52import java.util.List;
53import java.util.Iterator;
54import java.security.AccessController;
55
56import javax.security.auth.x500.X500Principal;
57import javax.security.auth.kerberos.KerberosPrincipal;
58
59import javax.net.ssl.*;
60import sun.security.x509.X500Name;
61import sun.misc.Regexp;
62import sun.misc.RegexpPool;
63import sun.net.www.HeaderParser;
64import sun.net.www.MessageHeader;
65import sun.net.www.http.HttpClient;
66import sun.security.action.*;
67
68import sun.security.util.HostnameChecker;
69import 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
120final 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 * Returns the cipher suite in use on this connection.
523 */
524 String getCipherSuite() {
525 return session.getCipherSuite();
526 }
527
528 /**
529 * Returns the certificate chain the client sent to the
530 * server, or null if the client did not authenticate.
531 */
532 public java.security.cert.Certificate [] getLocalCertificates() {
533 return session.getLocalCertificates();
534 }
535
536 /**
537 * Returns the certificate chain with which the server
538 * authenticated itself, or throw a SSLPeerUnverifiedException
539 * if the server did not authenticate.
540 */
541 java.security.cert.Certificate [] getServerCertificates()
542 throws SSLPeerUnverifiedException
543 {
544 return session.getPeerCertificates();
545 }
546
547 /**
548 * Returns the X.509 certificate chain with which the server
549 * authenticated itself, or null if the server did not authenticate.
550 */
551 javax.security.cert.X509Certificate [] getServerCertificateChain()
552 throws SSLPeerUnverifiedException
553 {
554 return session.getPeerCertificateChain();
555 }
556
557 /**
558 * Returns the principal with which the server authenticated
559 * itself, or throw a SSLPeerUnverifiedException if the
560 * server did not authenticate.
561 */
562 Principal getPeerPrincipal()
563 throws SSLPeerUnverifiedException
564 {
565 Principal principal;
566 try {
567 principal = session.getPeerPrincipal();
568 } catch (AbstractMethodError e) {
569 // if the provider does not support it, fallback to peer certs.
570 // return the X500Principal of the end-entity cert.
571 java.security.cert.Certificate[] certs =
572 session.getPeerCertificates();
573 principal = (X500Principal)
574 ((X509Certificate)certs[0]).getSubjectX500Principal();
575 }
576 return principal;
577 }
578
579 /**
580 * Returns the principal the client sent to the
581 * server, or null if the client did not authenticate.
582 */
583 Principal getLocalPrincipal()
584 {
585 Principal principal;
586 try {
587 principal = session.getLocalPrincipal();
588 } catch (AbstractMethodError e) {
589 principal = null;
590 // if the provider does not support it, fallback to local certs.
591 // return the X500Principal of the end-entity cert.
592 java.security.cert.Certificate[] certs =
593 session.getLocalCertificates();
594 if (certs != null) {
595 principal = (X500Principal)
596 ((X509Certificate)certs[0]).getSubjectX500Principal();
597 }
598 }
599 return principal;
600 }
601
602 /**
603 * This method implements the SSL HandshakeCompleted callback,
604 * remembering the resulting session so that it may be queried
605 * for the current cipher suite and peer certificates. Servers
606 * sometimes re-initiate handshaking, so the session in use on
607 * a given connection may change. When sessions change, so may
608 * peer identities and cipher suites.
609 */
610 public void handshakeCompleted(HandshakeCompletedEvent event)
611 {
612 session = event.getSession();
613 }
614
615 /**
616 * @return the proxy host being used for this client, or null
617 * if we're not going through a proxy
618 */
619 public String getProxyHostUsed() {
620 if (!needsTunneling()) {
621 return null;
622 } else {
623 return ((InetSocketAddress)proxy.address()).getHostName();
624 }
625 }
626
627 /**
628 * @return the proxy port being used for this client. Meaningless
629 * if getProxyHostUsed() gives null.
630 */
631 public int getProxyPortUsed() {
632 return (proxy == null || proxy.type() == Proxy.Type.DIRECT ||
633 proxy.type() == Proxy.Type.SOCKS)? -1:
634 ((InetSocketAddress)proxy.address()).getPort();
635 }
636}