Path: blob/master/test/jdk/sun/net/www/protocol/http/NTLMHeadTest.java
66646 views
/*1* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223/*24* @test25* @bug 827029026* @modules java.base/sun.net.www27* @library /test/lib28* @run main/othervm NTLMHeadTest SERVER29* @run main/othervm NTLMHeadTest PROXY30* @run main/othervm NTLMHeadTest TUNNEL31* @summary test for the incorrect logic in reading (and discarding) HTTP32* response body when processing NTLMSSP_CHALLENGE response33* (to CONNECT request) from proxy server. When this response is received34* by client, reset() is called on the connection to read and discard the35* response body. This code path was broken when initial client request36* uses HEAD method and HTTPS resource, in this case CONNECT is sent to37* proxy server (to establish TLS tunnel) and response body is not read38* from a socket (because initial method on client connection is HEAD).39* This does not cause problems with the majority of proxy servers because40* InputStream opened over the response socket is buffered with 8kb buffer41* size. Problem is only reproducible if the response size (headers +42* body) is larger than 8kb. The code path with HTTPS tunneling is checked43* with TUNNEL argument. Additional checks for HEAD handling are included44* for direct server (SERVER) and HTTP proxying (PROXY) code paths, in45* these (non-tunnel) cases client must NOT attempt to read response data46* (to not block on socket read) because HEAD is sent to server and47* NTLMSSP_CHALLENGE response includes Content-Length, but does not48* include the body.49*/5051import java.net.*;52import java.io.*;53import java.util.*;54import sun.net.www.MessageHeader;55import jdk.test.lib.net.URIBuilder;5657public class NTLMHeadTest {5859enum Mode { SERVER, PROXY, TUNNEL }6061static final int BODY_LEN = 8192;6263static final String RESP_SERVER_AUTH =64"HTTP/1.1 401 Unauthorized\r\n" +65"WWW-Authenticate: NTLM\r\n" +66"Connection: close\r\n" +67"Content-Length: " + BODY_LEN + "\r\n" +68"\r\n";6970static final String RESP_SERVER_NTLM =71"HTTP/1.1 401 Unauthorized\r\n" +72"WWW-Authenticate: NTLM TlRMTVNTUAACAAAAAAAAACgAAAABggAAU3J2Tm9uY2UAAAAAAAAAAA==\r\n" +73"Connection: Keep-Alive\r\n" +74"Content-Length: " + BODY_LEN + "\r\n" +75"\r\n";7677static final String RESP_SERVER_OR_PROXY_DEST =78"HTTP/1.1 200 OK\r\n" +79"Connection: close\r\n" +80"Content-Length: 42\r\n" +81"\r\n";8283static final String RESP_PROXY_AUTH =84"HTTP/1.1 407 Proxy Authentication Required\r\n" +85"Proxy-Authenticate: NTLM\r\n" +86"Proxy-Connection: close\r\n" +87"Connection: close\r\n" +88"Content-Length: " + BODY_LEN + "\r\n" +89"\r\n";9091static final String RESP_PROXY_NTLM =92"HTTP/1.1 407 Proxy Authentication Required\r\n" +93"Proxy-Authenticate: NTLM TlRMTVNTUAACAAAAAAAAACgAAAABggAAU3J2Tm9uY2UAAAAAAAAAAA==\r\n" +94"Proxy-Connection: Keep-Alive\r\n" +95"Connection: Keep-Alive\r\n" +96"Content-Length: " + BODY_LEN + "\r\n" +97"\r\n";9899static final String RESP_TUNNEL_AUTH =100"HTTP/1.1 407 Proxy Authentication Required\r\n" +101"Proxy-Authenticate: NTLM\r\n" +102"Proxy-Connection: close\r\n" +103"Connection: close\r\n" +104"Content-Length: " + BODY_LEN + "\r\n" +105"\r\n" +106generateBody(BODY_LEN);107108static final String RESP_TUNNEL_NTLM =109"HTTP/1.1 407 Proxy Authentication Required\r\n" +110"Proxy-Authenticate: NTLM TlRMTVNTUAACAAAAAAAAACgAAAABggAAU3J2Tm9uY2UAAAAAAAAAAA==\r\n" +111"Proxy-Connection: Keep-Alive\r\n" +112"Connection: Keep-Alive\r\n" +113"Content-Length: " + BODY_LEN + "\r\n" +114"\r\n" +115generateBody(BODY_LEN);116117static final String RESP_TUNNEL_ESTABLISHED =118"HTTP/1.1 200 Connection Established\r\n\r\n";119120public static void main(String[] args) throws Exception {121Authenticator.setDefault(new TestAuthenticator());122if (1 != args.length) {123throw new IllegalArgumentException("Mode value must be specified, one of: [SERVER, PROXY, TUNNEL]");124}125Mode mode = Mode.valueOf(args[0]);126System.out.println("Running with mode: " + mode);127switch (mode) {128case SERVER: testSever(); return;129case PROXY: testProxy(); return;130case TUNNEL: testTunnel(); return;131default: throw new IllegalArgumentException("Invalid mode: " + mode);132}133}134135static void testSever() throws Exception {136try (NTLMServer server = startServer(new ServerSocket(0, 0, InetAddress.getLoopbackAddress()), Mode.SERVER)) {137URL url = URIBuilder.newBuilder()138.scheme("http")139.loopback()140.port(server.getLocalPort())141.path("/")142.toURLUnchecked();143HttpURLConnection uc = (HttpURLConnection) url.openConnection();144uc.setRequestMethod("HEAD");145uc.getInputStream().readAllBytes();146}147}148149static void testProxy() throws Exception {150InetAddress loopback = InetAddress.getLoopbackAddress();151try (NTLMServer server = startServer(new ServerSocket(0, 0, loopback), Mode.PROXY)) {152SocketAddress proxyAddr = new InetSocketAddress(loopback, server.getLocalPort());153Proxy proxy = new Proxy(java.net.Proxy.Type.HTTP, proxyAddr);154URL url = URIBuilder.newBuilder()155.scheme("http")156.loopback()157.port(8080)158.path("/")159.toURLUnchecked();160HttpURLConnection uc = (HttpURLConnection) url.openConnection(proxy);161uc.setRequestMethod("HEAD");162uc.getInputStream().readAllBytes();163}164}165166static void testTunnel() throws Exception {167InetAddress loopback = InetAddress.getLoopbackAddress();168try (NTLMServer server = startServer(new ServerSocket(0, 0, loopback), Mode.TUNNEL)) {169SocketAddress proxyAddr = new InetSocketAddress(loopback, server.getLocalPort());170Proxy proxy = new Proxy(java.net.Proxy.Type.HTTP, proxyAddr);171URL url = URIBuilder.newBuilder()172.scheme("https")173.loopback()174.port(8443)175.path("/")176.toURLUnchecked();177HttpURLConnection uc = (HttpURLConnection) url.openConnection(proxy);178uc.setRequestMethod("HEAD");179try {180uc.getInputStream().readAllBytes();181} catch (IOException e) {182// can be SocketException or SSLHandshakeException183// Tunnel established and closed by server184System.out.println("Tunnel established successfully");185} catch (NoSuchElementException e) {186System.err.println("Error: cannot read 200 response code");187throw e;188}189}190}191192static class NTLMServer extends Thread implements AutoCloseable {193final ServerSocket ss;194final Mode mode;195volatile boolean closed;196197NTLMServer(ServerSocket serverSS, Mode mode) {198super();199setDaemon(true);200this.ss = serverSS;201this.mode = mode;202}203204int getLocalPort() { return ss.getLocalPort(); }205206@Override207public void run() {208boolean doing2ndStageNTLM = false;209while (!closed) {210try {211Socket s = ss.accept();212InputStream is = s.getInputStream();213OutputStream os = s.getOutputStream();214switch(mode) {215case SERVER:216doServer(is, os, doing2ndStageNTLM);217break;218case PROXY:219doProxy(is, os, doing2ndStageNTLM);220break;221case TUNNEL:222doTunnel(is, os, doing2ndStageNTLM);223break;224default: throw new IllegalArgumentException();225}226if (!doing2ndStageNTLM) {227doing2ndStageNTLM = true;228} else {229os.close();230}231} catch (IOException ioe) {232if (!closed) {233ioe.printStackTrace();234}235}236}237}238239@Override240public void close() {241if (closed) return;242synchronized(this) {243if (closed) return;244closed = true;245}246try { ss.close(); } catch (IOException x) { };247}248}249250static NTLMServer startServer(ServerSocket serverSS, Mode mode) {251NTLMServer server = new NTLMServer(serverSS, mode);252server.start();253return server;254}255256static String generateBody(int length) {257StringBuilder sb = new StringBuilder();258for(int i = 0; i < length; i++) {259sb.append(i % 10);260}261return sb.toString();262}263264static void doServer(InputStream is, OutputStream os, boolean doing2ndStageNTLM) throws IOException {265if (!doing2ndStageNTLM) {266new MessageHeader(is);267os.write(RESP_SERVER_AUTH.getBytes("ASCII"));268} else {269new MessageHeader(is);270os.write(RESP_SERVER_NTLM.getBytes("ASCII"));271new MessageHeader(is);272os.write(RESP_SERVER_OR_PROXY_DEST.getBytes("ASCII"));273}274}275276static void doProxy(InputStream is, OutputStream os, boolean doing2ndStageNTLM) throws IOException {277if (!doing2ndStageNTLM) {278new MessageHeader(is);279os.write(RESP_PROXY_AUTH.getBytes("ASCII"));280} else {281new MessageHeader(is);282os.write(RESP_PROXY_NTLM.getBytes("ASCII"));283new MessageHeader(is);284os.write(RESP_SERVER_OR_PROXY_DEST.getBytes("ASCII"));285}286}287288static void doTunnel(InputStream is, OutputStream os, boolean doing2ndStageNTLM) throws IOException {289if (!doing2ndStageNTLM) {290new MessageHeader(is);291os.write(RESP_TUNNEL_AUTH.getBytes("ASCII"));292} else {293new MessageHeader(is);294os.write(RESP_TUNNEL_NTLM.getBytes("ASCII"));295new MessageHeader(is);296os.write(RESP_TUNNEL_ESTABLISHED.getBytes("ASCII"));297}298}299300static class TestAuthenticator extends java.net.Authenticator {301protected PasswordAuthentication getPasswordAuthentication() {302return new PasswordAuthentication("test", "secret".toCharArray());303}304}305}306307308