Path: blob/master/test/jdk/sun/net/www/http/HttpURLConnection/DigestAuth.java
41154 views
/*1* Copyright (c) 2016, 2019, 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*/2223import com.sun.net.httpserver.HttpExchange;24import com.sun.net.httpserver.HttpHandler;25import com.sun.net.httpserver.HttpServer;26import java.io.BufferedReader;27import java.io.InputStreamReader;28import java.io.IOException;29import java.io.InputStream;30import java.net.Authenticator;31import java.net.InetAddress;32import java.net.InetSocketAddress;33import java.net.PasswordAuthentication;34import java.net.URL;35import java.net.URLConnection;36import java.util.List;3738/*39* @test40* @bug 813899041* @summary Tests for HTTP Digest auth42* The impl maintains a cache for auth info,43* the testcases run in a separate JVM to avoid cache hits44* @modules jdk.httpserver45* @run main/othervm DigestAuth good46* @run main/othervm DigestAuth only_nonce47* @run main/othervm DigestAuth sha148* @run main/othervm DigestAuth no_header49* @run main/othervm DigestAuth no_nonce50* @run main/othervm DigestAuth no_qop51* @run main/othervm DigestAuth invalid_alg52* @run main/othervm DigestAuth validate_server53* @run main/othervm DigestAuth validate_server_no_qop54*/55public class DigestAuth {5657static final String EXPECT_FAILURE = null;58static final String EXPECT_DIGEST = "Digest";59static final String REALM = "[email protected]";60static final String NEXT_NONCE = "40f2e879449675f288476d772627370a";6162static final String GOOD_WWW_AUTH_HEADER = "Digest "63+ "realm=\"[email protected]\", "64+ "qop=\"auth,auth-int\", "65+ "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "66+ "opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"";6768static final String GOOD_WWW_AUTH_HEADER_NO_QOP = "Digest "69+ "realm=\"[email protected]\", "70+ "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "71+ "opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"";7273static final String WWW_AUTH_HEADER_NO_NONCE = "Digest "74+ "realm=\"[email protected]\", "75+ "qop=\"auth,auth-int\", "76+ "opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"";7778static final String WWW_AUTH_HEADER_NO_QOP = "Digest "79+ "realm=\"[email protected]\", "80+ "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "81+ "opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"";8283static final String WWW_AUTH_HEADER_ONLY_NONCE = "Digest "84+ "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\"";8586static final String WWW_AUTH_HEADER_SHA1 = "Digest "87+ "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "88+ "algorithm=\"SHA1\"";8990static final String WWW_AUTH_HEADER_INVALID_ALGORITHM = "Digest "91+ "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "92+ "algorithm=\"SHA123\"";9394static final String AUTH_INFO_HEADER_NO_QOP_FIRST =95"nextnonce=\"" + NEXT_NONCE + "\", "96+ "rspauth=\"ee85bc4315d8b18757809f1a8b9382d8\"";9798static final String AUTH_INFO_HEADER_NO_QOP_SECOND =99"rspauth=\"12f2fa12841b3775b6054576722446b2\"";100101static final String AUTH_INFO_HEADER_WRONG_DIGEST =102"nextnonce=\"" + NEXT_NONCE + "\", "103+ "rspauth=\"7327570c586207eca2afae94fc20903d\", "104+ "cnonce=\"0a4f113b\", "105+ "nc=00000001, "106+ "qop=auth";107108public static void main(String[] args) throws Exception {109if (args.length == 0) {110throw new RuntimeException("No testcase specified");111}112String testcase = args[0];113114// start a local HTTP server115try (LocalHttpServer server = LocalHttpServer.startServer()) {116117// set authenticator118AuthenticatorImpl auth = new AuthenticatorImpl();119Authenticator.setDefault(auth);120121String url = String.format("http://%s/test/", server.getAuthority());122123boolean success = true;124switch (testcase) {125case "good":126// server returns a good WWW-Authenticate header127server.setWWWAuthHeader(GOOD_WWW_AUTH_HEADER);128success = testAuth(url, auth, EXPECT_DIGEST);129if (auth.lastRequestedPrompt == null ||130!auth.lastRequestedPrompt.equals(REALM)) {131System.out.println("Unexpected realm: "132+ auth.lastRequestedPrompt);133success = false;134}135break;136case "validate_server":137// enable processing Authentication-Info headers138System.setProperty("http.auth.digest.validateServer",139"true");140141/* Server returns good WWW-Authenticate142* and Authentication-Info headers with wrong digest143*/144server.setWWWAuthHeader(GOOD_WWW_AUTH_HEADER);145server.setAuthInfoHeader(AUTH_INFO_HEADER_WRONG_DIGEST);146success = testAuth(url, auth, EXPECT_FAILURE);147if (auth.lastRequestedPrompt == null ||148!auth.lastRequestedPrompt.equals(REALM)) {149System.out.println("Unexpected realm: "150+ auth.lastRequestedPrompt);151success = false;152}153break;154case "validate_server_no_qop":155// enable processing Authentication-Info headers156System.setProperty("http.auth.digest.validateServer",157"true");158159/* Server returns good both WWW-Authenticate160* and Authentication-Info headers without any qop field,161* so that client-nonce should not be taked into account,162* and connection should succeed.163*/164server.setWWWAuthHeader(GOOD_WWW_AUTH_HEADER_NO_QOP);165server.setAuthInfoHeader(AUTH_INFO_HEADER_NO_QOP_FIRST);166success = testAuth(url, auth, EXPECT_DIGEST);167if (auth.lastRequestedPrompt == null ||168!auth.lastRequestedPrompt.equals(REALM)) {169System.out.println("Unexpected realm: "170+ auth.lastRequestedPrompt);171success = false;172}173174// connect again and check if nextnonce was used175server.setAuthInfoHeader(AUTH_INFO_HEADER_NO_QOP_SECOND);176success &= testAuth(url, auth, EXPECT_DIGEST);177if (!NEXT_NONCE.equals(server.lastRequestedNonce)) {178System.out.println("Unexpected next nonce: "179+ server.lastRequestedNonce);180success = false;181}182break;183case "only_nonce":184/* Server returns a good WWW-Authenticate header185* which contains only nonce (no realm set).186*187* Realm from WWW-Authenticate header is passed to188* authenticator which can use it as a prompt189* when it asks a user for credentials.190*191* It's fine if an HTTP client doesn't fail if no realm set,192* and delegates making a decision to authenticator/user.193*/194server.setWWWAuthHeader(WWW_AUTH_HEADER_ONLY_NONCE);195success = testAuth(url, auth, EXPECT_DIGEST);196if (auth.lastRequestedPrompt != null &&197!auth.lastRequestedPrompt.trim().isEmpty()) {198System.out.println("Unexpected realm: "199+ auth.lastRequestedPrompt);200success = false;201}202break;203case "sha1":204// server returns a good WWW-Authenticate header with SHA-1205server.setWWWAuthHeader(WWW_AUTH_HEADER_SHA1);206success = testAuth(url, auth, EXPECT_DIGEST);207break;208case "no_header":209// server returns no WWW-Authenticate header210success = testAuth(url, auth, EXPECT_FAILURE);211if (auth.lastRequestedScheme != null) {212System.out.println("Unexpected scheme: "213+ auth.lastRequestedScheme);214success = false;215}216break;217case "no_nonce":218// server returns a wrong WWW-Authenticate header (no nonce)219server.setWWWAuthHeader(WWW_AUTH_HEADER_NO_NONCE);220success = testAuth(url, auth, EXPECT_FAILURE);221break;222case "invalid_alg":223// server returns a wrong WWW-Authenticate header224// (invalid hash algorithm)225server.setWWWAuthHeader(WWW_AUTH_HEADER_INVALID_ALGORITHM);226success = testAuth(url, auth, EXPECT_FAILURE);227break;228case "no_qop":229// server returns a good WWW-Authenticate header230// without QOPs231server.setWWWAuthHeader(WWW_AUTH_HEADER_NO_QOP);232success = testAuth(url, auth, EXPECT_DIGEST);233break;234default:235throw new RuntimeException("Unexpected testcase: "236+ testcase);237}238239if (!success) {240throw new RuntimeException("Test failed");241}242}243244System.out.println("Test passed");245}246247static boolean testAuth(String url, AuthenticatorImpl auth,248String expectedScheme) {249250try {251System.out.printf("Connect to %s, expected auth scheme is '%s'%n",252url, expectedScheme);253load(url);254255if (expectedScheme == null) {256System.out.println("Unexpected successful connection");257return false;258}259260System.out.printf("Actual auth scheme is '%s'%n",261auth.lastRequestedScheme);262if (!expectedScheme.equalsIgnoreCase(auth.lastRequestedScheme)) {263System.out.println("Unexpected auth scheme");264return false;265}266} catch (IOException e) {267if (expectedScheme != null) {268System.out.println("Unexpected exception: " + e);269e.printStackTrace(System.out);270return false;271}272System.out.println("Expected exception: " + e);273}274275return true;276}277278static void load(String url) throws IOException {279URLConnection conn = new URL(url).openConnection();280conn.setUseCaches(false);281try (BufferedReader reader = new BufferedReader(282new InputStreamReader(conn.getInputStream()))) {283284String line = reader.readLine();285if (line == null) {286throw new IOException("Couldn't read response");287}288do {289System.out.println(line);290} while ((line = reader.readLine()) != null);291}292}293294private static class AuthenticatorImpl extends Authenticator {295296private String lastRequestedScheme;297private String lastRequestedPrompt;298299@Override300public PasswordAuthentication getPasswordAuthentication() {301lastRequestedScheme = getRequestingScheme();302lastRequestedPrompt = getRequestingPrompt();303System.out.println("AuthenticatorImpl: requested "304+ lastRequestedScheme);305306return new PasswordAuthentication("Mufasa",307"Circle Of Life".toCharArray());308}309}310311// local HTTP server which pretends to support HTTP Digest auth312static class LocalHttpServer implements HttpHandler, AutoCloseable {313314private final HttpServer server;315private volatile String wwwAuthHeader = null;316private volatile String authInfoHeader = null;317private volatile String lastRequestedNonce;318319private LocalHttpServer(HttpServer server) {320this.server = server;321}322323public String getAuthority() {324InetAddress address = server.getAddress().getAddress();325String hostaddr = address.isAnyLocalAddress()326? "localhost" : address.getHostAddress();327if (hostaddr.indexOf(':') > -1) {328hostaddr = "[" + hostaddr + "]";329}330return hostaddr + ":" + getPort();331}332333void setWWWAuthHeader(String wwwAuthHeader) {334this.wwwAuthHeader = wwwAuthHeader;335}336337void setAuthInfoHeader(String authInfoHeader) {338this.authInfoHeader = authInfoHeader;339}340341static LocalHttpServer startServer() throws IOException {342InetAddress loopback = InetAddress.getLoopbackAddress();343HttpServer httpServer = HttpServer.create(344new InetSocketAddress(loopback, 0), 0);345LocalHttpServer localHttpServer = new LocalHttpServer(httpServer);346localHttpServer.start();347348return localHttpServer;349}350351void start() {352server.createContext("/test", this);353server.start();354System.out.println("HttpServer: started on port " + getAuthority());355}356357void stop() {358server.stop(0);359System.out.println("HttpServer: stopped");360}361362int getPort() {363return server.getAddress().getPort();364}365366@Override367public void handle(HttpExchange t) throws IOException {368System.out.println("HttpServer: handle connection");369370// read a request371try (InputStream is = t.getRequestBody()) {372while (is.read() > 0);373}374375try {376List<String> headers = t.getRequestHeaders()377.get("Authorization");378String header = "";379if (headers != null && !headers.isEmpty()) {380header = headers.get(0).trim().toLowerCase();381}382if (header.startsWith("digest")) {383if (authInfoHeader != null) {384t.getResponseHeaders().add("Authentication-Info",385authInfoHeader);386}387lastRequestedNonce = findParameter(header, "nonce");388byte[] output = "hello".getBytes();389t.sendResponseHeaders(200, output.length);390t.getResponseBody().write(output);391System.out.println("HttpServer: return 200");392} else {393if (wwwAuthHeader != null) {394t.getResponseHeaders().add(395"WWW-Authenticate", wwwAuthHeader);396}397byte[] output = "forbidden".getBytes();398t.sendResponseHeaders(401, output.length);399t.getResponseBody().write(output);400System.out.println("HttpServer: return 401");401}402} catch (IOException e) {403System.out.println("HttpServer: exception: " + e);404System.out.println("HttpServer: return 500");405t.sendResponseHeaders(500, 0);406} finally {407t.close();408}409}410411private static String findParameter(String header, String name) {412name = name.toLowerCase();413if (header != null) {414String[] params = header.split("\\s");415for (String param : params) {416param = param.trim().toLowerCase();417if (param.startsWith(name)) {418String[] parts = param.split("=");419if (parts.length > 1) {420return parts[1]421.replaceAll("\"", "").replaceAll(",", "");422}423}424}425}426return null;427}428429@Override430public void close() {431stop();432}433}434}435436437