1266N/A/*
3579N/A * Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
1266N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
1266N/A *
1266N/A * This code is free software; you can redistribute it and/or modify it
1266N/A * under the terms of the GNU General Public License version 2 only, as
1266N/A * published by the Free Software Foundation.
1266N/A *
1266N/A * This code is distributed in the hope that it will be useful, but WITHOUT
1266N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1266N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
1266N/A * version 2 for more details (a copy is included in the LICENSE file that
1266N/A * accompanied this code).
1266N/A *
1266N/A * You should have received a copy of the GNU General Public License version
1266N/A * 2 along with this work; if not, write to the Free Software Foundation,
1266N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
1266N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
1266N/A */
1266N/A
1266N/A/*
1266N/A * @test
2233N/A * @bug 6578647 6829283
3579N/A * @run main/othervm HttpNegotiateServer
1266N/A * @summary Undefined requesting URL in java.net.Authenticator.getPasswordAuthentication()
2233N/A * @summary HTTP/Negotiate: Authenticator triggered again when user cancels the first one
1266N/A */
1266N/A
1266N/Aimport com.sun.net.httpserver.Headers;
1266N/Aimport com.sun.net.httpserver.HttpContext;
1266N/Aimport com.sun.net.httpserver.HttpExchange;
1266N/Aimport com.sun.net.httpserver.HttpHandler;
1266N/Aimport com.sun.net.httpserver.HttpServer;
1266N/Aimport com.sun.net.httpserver.HttpPrincipal;
1266N/Aimport com.sun.security.auth.module.Krb5LoginModule;
1266N/Aimport java.io.BufferedReader;
2233N/Aimport java.io.File;
2233N/Aimport java.io.FileOutputStream;
1266N/Aimport java.io.IOException;
1266N/Aimport java.io.InputStreamReader;
1266N/Aimport java.net.HttpURLConnection;
1266N/Aimport java.net.InetSocketAddress;
1266N/Aimport java.net.PasswordAuthentication;
1266N/Aimport java.net.Proxy;
1266N/Aimport java.net.URL;
1266N/Aimport java.security.PrivilegedExceptionAction;
1266N/Aimport java.util.HashMap;
1266N/Aimport java.util.Map;
1266N/Aimport javax.security.auth.Subject;
1266N/Aimport org.ietf.jgss.GSSContext;
1266N/Aimport org.ietf.jgss.GSSCredential;
1266N/Aimport org.ietf.jgss.GSSManager;
1266N/Aimport sun.security.jgss.GSSUtil;
1266N/Aimport sun.security.krb5.Config;
1266N/A
1266N/A/**
1266N/A * Basic JGSS/krb5 test with 3 parties: client, server, backend server. Each
1266N/A * party uses JAAS login to get subjects and executes JGSS calls using
1266N/A * Subject.doAs.
1266N/A */
1300N/Apublic class HttpNegotiateServer {
1266N/A
1266N/A // Two realm, web server in one, proxy server in another
1266N/A final static String REALM_WEB = "WEB.DOMAIN";
1266N/A final static String REALM_PROXY = "PROXY.DOMAIN";
1266N/A final static String KRB5_CONF = "web.conf";
1266N/A final static String KRB5_TAB = "web.ktab";
1266N/A
1266N/A // user principals
1266N/A final static String WEB_USER = "web";
1266N/A final static char[] WEB_PASS = "webby".toCharArray();
1266N/A final static String PROXY_USER = "pro";
1266N/A final static char[] PROXY_PASS = "proxy".toCharArray();
3882N/A
1266N/A
1266N/A final static String WEB_HOST = "host.web.domain";
1266N/A final static String PROXY_HOST = "host.proxy.domain";
1266N/A
1266N/A // web page content
1266N/A final static String CONTENT = "Hello, World!";
1266N/A
2233N/A // For 6829283, count how many times the Authenticator is called.
2233N/A static int count = 0;
2233N/A
3882N/A static int webPort, proxyPort;
3882N/A
1266N/A // URLs for web test, proxy test. The proxy server is not a real proxy
1266N/A // since it fakes the same content for any URL. :)
3882N/A static URL webUrl, proxyUrl;
1266N/A
1266N/A /**
1266N/A * This Authenticator checks everything:
1266N/A * scheme, protocol, requestor type, host, port, and url
1266N/A */
1266N/A static class KnowAllAuthenticator extends java.net.Authenticator {
1266N/A public PasswordAuthentication getPasswordAuthentication () {
1266N/A if (!getRequestingScheme().equalsIgnoreCase("Negotiate")) {
1266N/A throw new RuntimeException("Bad scheme");
1266N/A }
1266N/A if (!getRequestingProtocol().equalsIgnoreCase("HTTP")) {
1266N/A throw new RuntimeException("Bad protocol");
1266N/A }
1266N/A if (getRequestorType() == RequestorType.SERVER) {
1266N/A if (!this.getRequestingHost().equalsIgnoreCase(webUrl.getHost())) {
1266N/A throw new RuntimeException("Bad host");
1266N/A }
1266N/A if (this.getRequestingPort() != webUrl.getPort()) {
1266N/A throw new RuntimeException("Bad port");
1266N/A }
1266N/A if (!this.getRequestingURL().equals(webUrl)) {
1266N/A throw new RuntimeException("Bad url");
1266N/A }
1266N/A return new PasswordAuthentication(
1266N/A WEB_USER+"@"+REALM_WEB, WEB_PASS);
1266N/A } else if (getRequestorType() == RequestorType.PROXY) {
1266N/A if (!this.getRequestingHost().equalsIgnoreCase(PROXY_HOST)) {
1266N/A throw new RuntimeException("Bad host");
1266N/A }
3882N/A if (this.getRequestingPort() != proxyPort) {
1266N/A throw new RuntimeException("Bad port");
1266N/A }
1266N/A if (!this.getRequestingURL().equals(proxyUrl)) {
1266N/A throw new RuntimeException("Bad url");
1266N/A }
1266N/A return new PasswordAuthentication(
1266N/A PROXY_USER+"@"+REALM_PROXY, PROXY_PASS);
1266N/A } else {
1266N/A throw new RuntimeException("Bad requster type");
1266N/A }
1266N/A }
1266N/A }
1266N/A
2233N/A /**
2233N/A * This Authenticator knows nothing
2233N/A */
2233N/A static class KnowNothingAuthenticator extends java.net.Authenticator {
2233N/A @Override
2233N/A public PasswordAuthentication getPasswordAuthentication () {
2233N/A HttpNegotiateServer.count++;
2233N/A return null;
2233N/A }
2233N/A }
2233N/A
1266N/A public static void main(String[] args)
1266N/A throws Exception {
1266N/A
1300N/A KDC kdcw = KDC.create(REALM_WEB);
1266N/A kdcw.addPrincipal(WEB_USER, WEB_PASS);
1266N/A kdcw.addPrincipalRandKey("krbtgt/" + REALM_WEB);
1266N/A kdcw.addPrincipalRandKey("HTTP/" + WEB_HOST);
1266N/A
1300N/A KDC kdcp = KDC.create(REALM_PROXY);
1266N/A kdcp.addPrincipal(PROXY_USER, PROXY_PASS);
1266N/A kdcp.addPrincipalRandKey("krbtgt/" + REALM_PROXY);
1266N/A kdcp.addPrincipalRandKey("HTTP/" + PROXY_HOST);
1266N/A
1266N/A KDC.saveConfig(KRB5_CONF, kdcw, kdcp,
1266N/A "default_keytab_name = " + KRB5_TAB,
1266N/A "[domain_realm]",
1266N/A "",
1266N/A ".web.domain="+REALM_WEB,
1266N/A ".proxy.domain="+REALM_PROXY);
1266N/A
1266N/A System.setProperty("java.security.krb5.conf", KRB5_CONF);
1266N/A Config.refresh();
2233N/A KDC.writeMultiKtab(KRB5_TAB, kdcw, kdcp);
2233N/A
2233N/A // Write a customized JAAS conf file, so that any kinit cache
2233N/A // will be ignored.
2233N/A System.setProperty("java.security.auth.login.config", OneKDC.JAAS_CONF);
2233N/A File f = new File(OneKDC.JAAS_CONF);
2233N/A FileOutputStream fos = new FileOutputStream(f);
2233N/A fos.write((
2233N/A "com.sun.security.jgss.krb5.initiate {\n" +
2233N/A " com.sun.security.auth.module.Krb5LoginModule required;\n};\n"
2233N/A ).getBytes());
2233N/A fos.close();
2233N/A f.deleteOnExit();
1266N/A
3882N/A HttpServer h1 = httpd("Negotiate", false,
1266N/A "HTTP/" + WEB_HOST + "@" + REALM_WEB, KRB5_TAB);
3882N/A webPort = h1.getAddress().getPort();
3882N/A HttpServer h2 = httpd("Negotiate", true,
1266N/A "HTTP/" + PROXY_HOST + "@" + REALM_PROXY, KRB5_TAB);
3882N/A proxyPort = h2.getAddress().getPort();
3882N/A
3882N/A webUrl = new URL("http://" + WEB_HOST +":" + webPort + "/a/b/c");
3882N/A proxyUrl = new URL("http://nosuchplace/a/b/c");
1266N/A
1266N/A try {
2233N/A Exception e1 = null, e2 = null;
2233N/A try {
2233N/A test6578647();
2233N/A } catch (Exception e) {
2233N/A e1 = e;
2233N/A e.printStackTrace();
1266N/A }
2233N/A try {
2233N/A test6829283();
2233N/A } catch (Exception e) {
2233N/A e2 = e;
2233N/A e.printStackTrace();
2233N/A }
2233N/A if (e1 != null || e2 != null) {
2233N/A throw new RuntimeException("Test error");
1266N/A }
1266N/A } finally {
1266N/A // Must stop. Seems there's no HttpServer.startAsDaemon()
1266N/A if (h1 != null) h1.stop(0);
1266N/A if (h2 != null) h2.stop(0);
1266N/A }
1266N/A }
1266N/A
2233N/A static void test6578647() throws Exception {
2233N/A BufferedReader reader;
2233N/A java.net.Authenticator.setDefault(new KnowAllAuthenticator());
2233N/A
2233N/A reader = new BufferedReader(new InputStreamReader(
2233N/A webUrl.openConnection().getInputStream()));
2233N/A if (!reader.readLine().equals(CONTENT)) {
2233N/A throw new RuntimeException("Bad content");
2233N/A }
2233N/A
2233N/A reader = new BufferedReader(new InputStreamReader(
2233N/A proxyUrl.openConnection(
2233N/A new Proxy(Proxy.Type.HTTP,
3882N/A new InetSocketAddress(PROXY_HOST, proxyPort)))
2233N/A .getInputStream()));
2233N/A if (!reader.readLine().equals(CONTENT)) {
2233N/A throw new RuntimeException("Bad content");
2233N/A }
2233N/A }
2233N/A
2233N/A static void test6829283() throws Exception {
2233N/A BufferedReader reader;
2233N/A java.net.Authenticator.setDefault(new KnowNothingAuthenticator());
2233N/A try {
2233N/A new BufferedReader(new InputStreamReader(
2233N/A webUrl.openConnection().getInputStream()));
2233N/A } catch (IOException ioe) {
2233N/A // Will fail since no username and password is provided.
2233N/A }
2233N/A if (count > 1) {
2233N/A throw new RuntimeException("Authenticator called twice");
2233N/A }
2233N/A }
2233N/A
1266N/A /**
1266N/A * Creates and starts an HTTP or proxy server that requires
1266N/A * Negotiate authentication.
1266N/A * @param scheme "Negotiate" or "Kerberos"
1266N/A * @param principal the krb5 service principal the server runs with
1266N/A * @return the server
1266N/A */
3882N/A public static HttpServer httpd(String scheme, boolean proxy,
1266N/A String principal, String ktab) throws Exception {
1266N/A MyHttpHandler h = new MyHttpHandler();
3882N/A HttpServer server = HttpServer.create(new InetSocketAddress(0), 0);
1266N/A HttpContext hc = server.createContext("/", h);
1266N/A hc.setAuthenticator(new MyServerAuthenticator(
1266N/A proxy, scheme, principal, ktab));
1266N/A server.start();
1266N/A return server;
1266N/A }
1266N/A
1266N/A static class MyHttpHandler implements HttpHandler {
1266N/A public void handle(HttpExchange t) throws IOException {
1266N/A t.sendResponseHeaders(200, 0);
1266N/A t.getResponseBody().write(CONTENT.getBytes());
1266N/A t.close();
1266N/A }
1266N/A }
1266N/A
1266N/A static class MyServerAuthenticator
1266N/A extends com.sun.net.httpserver.Authenticator {
1266N/A Subject s = new Subject();
1266N/A GSSManager m = null;
1266N/A GSSCredential cred = null;
1266N/A String scheme = null;
1266N/A String reqHdr = "WWW-Authenticate";
1266N/A String respHdr = "Authorization";
1266N/A int err = HttpURLConnection.HTTP_UNAUTHORIZED;
1266N/A
1266N/A public MyServerAuthenticator(boolean proxy, String scheme,
1266N/A String principal, String ktab) throws Exception {
1266N/A
1266N/A this.scheme = scheme;
1266N/A if (proxy) {
1266N/A reqHdr = "Proxy-Authenticate";
1266N/A respHdr = "Proxy-Authorization";
1266N/A err = HttpURLConnection.HTTP_PROXY_AUTH;
1266N/A }
1266N/A
1266N/A Krb5LoginModule krb5 = new Krb5LoginModule();
3388N/A Map<String, String> map = new HashMap<>();
3388N/A Map<String, Object> shared = new HashMap<>();
1266N/A
1266N/A map.put("storeKey", "true");
1266N/A map.put("isInitiator", "false");
1266N/A map.put("useKeyTab", "true");
1266N/A map.put("keyTab", ktab);
1266N/A map.put("principal", principal);
1266N/A krb5.initialize(s, null, shared, map);
1266N/A krb5.login();
1266N/A krb5.commit();
1266N/A m = GSSManager.getInstance();
1266N/A cred = Subject.doAs(s, new PrivilegedExceptionAction<GSSCredential>() {
1266N/A @Override
1266N/A public GSSCredential run() throws Exception {
1266N/A System.err.println("Creating GSSCredential");
1266N/A return m.createCredential(
1266N/A null,
1266N/A GSSCredential.INDEFINITE_LIFETIME,
1266N/A MyServerAuthenticator.this.scheme.equalsIgnoreCase("Negotiate")?
1266N/A GSSUtil.GSS_SPNEGO_MECH_OID:
1266N/A GSSUtil.GSS_KRB5_MECH_OID,
1266N/A GSSCredential.ACCEPT_ONLY);
1266N/A }
1266N/A });
1266N/A }
1266N/A
1266N/A @Override
1266N/A public Result authenticate(HttpExchange exch) {
1266N/A // The GSContext is stored in an HttpContext attribute named
1266N/A // "GSSContext" and is created at the first request.
1266N/A GSSContext c = null;
1266N/A String auth = exch.getRequestHeaders().getFirst(respHdr);
1266N/A try {
1266N/A c = (GSSContext)exch.getHttpContext().getAttributes().get("GSSContext");
1266N/A if (auth == null) { // First request
1266N/A Headers map = exch.getResponseHeaders();
1266N/A map.set (reqHdr, scheme); // Challenge!
1266N/A c = Subject.doAs(s, new PrivilegedExceptionAction<GSSContext>() {
1266N/A @Override
1266N/A public GSSContext run() throws Exception {
1266N/A return m.createContext(cred);
1266N/A }
1266N/A });
1266N/A exch.getHttpContext().getAttributes().put("GSSContext", c);
1266N/A return new com.sun.net.httpserver.Authenticator.Retry(err);
1266N/A } else { // Later requests
1266N/A byte[] token = new sun.misc.BASE64Decoder()
1266N/A .decodeBuffer(auth.split(" ")[1]);
1266N/A token = c.acceptSecContext(token, 0, token.length);
1266N/A Headers map = exch.getResponseHeaders();
1266N/A map.set (reqHdr, scheme + " " + new sun.misc.BASE64Encoder()
1266N/A .encode(token).replaceAll("\\s", ""));
1266N/A if (c.isEstablished()) {
1266N/A return new com.sun.net.httpserver.Authenticator.Success(
1266N/A new HttpPrincipal(c.getSrcName().toString(), ""));
1266N/A } else {
1266N/A return new com.sun.net.httpserver.Authenticator.Retry(err);
1266N/A }
1266N/A }
1266N/A } catch (Exception e) {
1266N/A throw new RuntimeException(e);
1266N/A }
1266N/A }
1266N/A }
1266N/A}