jdk/test/sun/security/krb5/auto/HttpNegotiateServer.java
changeset 2942 37d9baeb7518
child 3046 dd50d75d88e6
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/security/krb5/auto/HttpNegotiateServer.java	Tue Jun 09 14:17:05 2009 +0800
@@ -0,0 +1,341 @@
+/*
+ * Copyright 2009 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test
+ * @bug 6578647
+ * @summary Undefined requesting URL in java.net.Authenticator.getPasswordAuthentication()
+ * @run main/othervm -Dsun.net.spi.nameservice.provider.1=ns,mock HttpNegotiateServer
+ */
+
+import com.sun.net.httpserver.Headers;
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpPrincipal;
+import com.sun.security.auth.module.Krb5LoginModule;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.InetAddress;
+import java.net.PasswordAuthentication;
+import java.net.Proxy;
+import java.net.URL;
+import java.security.PrivilegedExceptionAction;
+import java.net.UnknownHostException;
+import java.util.HashMap;
+import java.util.Map;
+import javax.security.auth.Subject;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSManager;
+import sun.security.jgss.GSSUtil;
+import sun.net.spi.nameservice.NameService;
+import sun.net.spi.nameservice.NameServiceDescriptor;
+import sun.security.krb5.Config;
+
+/**
+ * Basic JGSS/krb5 test with 3 parties: client, server, backend server. Each
+ * party uses JAAS login to get subjects and executes JGSS calls using
+ * Subject.doAs.
+ */
+public class HttpNegotiateServer implements NameServiceDescriptor {
+
+    // Two realm, web server in one, proxy server in another
+    final static String REALM_WEB = "WEB.DOMAIN";
+    final static String REALM_PROXY = "PROXY.DOMAIN";
+    final static String KRB5_CONF = "web.conf";
+    final static String KRB5_TAB = "web.ktab";
+
+    // user principals
+    final static String WEB_USER = "web";
+    final static char[] WEB_PASS = "webby".toCharArray();
+    final static String PROXY_USER = "pro";
+    final static char[] PROXY_PASS = "proxy".toCharArray();
+    final static int WEB_PORT = 17840;
+
+    final static String WEB_HOST = "host.web.domain";
+    final static String PROXY_HOST = "host.proxy.domain";
+    final static int PROXY_PORT = 17841;
+
+    // web page content
+    final static String CONTENT = "Hello, World!";
+
+    // URLs for web test, proxy test. The proxy server is not a real proxy
+    // since it fakes the same content for any URL. :)
+    final static URL webUrl, proxyUrl;
+    static {
+        URL u1 = null, u2 = null;
+        try {
+            u1 = new URL("http://" + WEB_HOST +":" + WEB_PORT + "/a/b/c");
+            u2 = new URL("http://nosuchplace/a/b/c");
+        } catch (Exception e) {
+        }
+        webUrl = u1; proxyUrl = u2;
+    }
+
+    /**
+     * This Authenticator checks everything:
+     * scheme, protocol, requestor type, host, port, and url
+     */
+    static class KnowAllAuthenticator extends java.net.Authenticator {
+        public PasswordAuthentication getPasswordAuthentication () {
+            if (!getRequestingScheme().equalsIgnoreCase("Negotiate")) {
+                throw new RuntimeException("Bad scheme");
+            }
+            if (!getRequestingProtocol().equalsIgnoreCase("HTTP")) {
+                throw new RuntimeException("Bad protocol");
+            }
+            if (getRequestorType() == RequestorType.SERVER) {
+                if (!this.getRequestingHost().equalsIgnoreCase(webUrl.getHost())) {
+                    throw new RuntimeException("Bad host");
+                }
+                if (this.getRequestingPort() != webUrl.getPort()) {
+                    throw new RuntimeException("Bad port");
+                }
+                if (!this.getRequestingURL().equals(webUrl)) {
+                    throw new RuntimeException("Bad url");
+                }
+                return new PasswordAuthentication(
+                        WEB_USER+"@"+REALM_WEB, WEB_PASS);
+            } else if (getRequestorType() == RequestorType.PROXY) {
+                if (!this.getRequestingHost().equalsIgnoreCase(PROXY_HOST)) {
+                    throw new RuntimeException("Bad host");
+                }
+                if (this.getRequestingPort() != PROXY_PORT) {
+                    throw new RuntimeException("Bad port");
+                }
+                if (!this.getRequestingURL().equals(proxyUrl)) {
+                    throw new RuntimeException("Bad url");
+                }
+                return new PasswordAuthentication(
+                        PROXY_USER+"@"+REALM_PROXY, PROXY_PASS);
+            } else  {
+                throw new RuntimeException("Bad requster type");
+            }
+        }
+    }
+
+    public static void main(String[] args)
+            throws Exception {
+
+        KDC kdcw = new KDC(REALM_WEB, 0, true);
+        kdcw.addPrincipal(WEB_USER, WEB_PASS);
+        kdcw.addPrincipalRandKey("krbtgt/" + REALM_WEB);
+        kdcw.addPrincipalRandKey("HTTP/" + WEB_HOST);
+
+        KDC kdcp = new KDC(REALM_PROXY, 0, true);
+        kdcp.addPrincipal(PROXY_USER, PROXY_PASS);
+        kdcp.addPrincipalRandKey("krbtgt/" + REALM_PROXY);
+        kdcp.addPrincipalRandKey("HTTP/" + PROXY_HOST);
+
+        KDC.writeMultiKtab(KRB5_TAB, kdcw, kdcp);
+        KDC.saveConfig(KRB5_CONF, kdcw, kdcp,
+                "default_keytab_name = " + KRB5_TAB,
+                "[domain_realm]",
+                "",
+                ".web.domain="+REALM_WEB,
+                ".proxy.domain="+REALM_PROXY);
+
+        System.setProperty("java.security.krb5.conf", KRB5_CONF);
+        Config.refresh();
+
+        HttpServer h1 = httpd(WEB_PORT, "Negotiate", false,
+                "HTTP/" + WEB_HOST + "@" + REALM_WEB, KRB5_TAB);
+        HttpServer h2 = httpd(PROXY_PORT, "Negotiate", true,
+                "HTTP/" + PROXY_HOST + "@" + REALM_PROXY, KRB5_TAB);
+
+        try {
+
+            BufferedReader reader;
+            java.net.Authenticator.setDefault(new KnowAllAuthenticator());
+
+            reader = new BufferedReader(new InputStreamReader(
+                    webUrl.openConnection().getInputStream()));
+            if (!reader.readLine().equals(CONTENT)) {
+                throw new RuntimeException("Bad content");
+            }
+
+            reader = new BufferedReader(new InputStreamReader(
+                    proxyUrl.openConnection(
+                    new Proxy(Proxy.Type.HTTP,
+                        new InetSocketAddress(PROXY_HOST, PROXY_PORT)))
+                    .getInputStream()));
+            if (!reader.readLine().equals(CONTENT)) {
+                throw new RuntimeException("Bad content");
+            }
+        } finally {
+            // Must stop. Seems there's no HttpServer.startAsDaemon()
+            if (h1 != null) h1.stop(0);
+            if (h2 != null) h2.stop(0);
+        }
+    }
+
+    /**
+     * Creates and starts an HTTP or proxy server that requires
+     * Negotiate authentication.
+     * @param scheme "Negotiate" or "Kerberos"
+     * @param principal the krb5 service principal the server runs with
+     * @return the server
+     */
+    public static HttpServer httpd(int port, String scheme, boolean proxy,
+            String principal, String ktab) throws Exception {
+        MyHttpHandler h = new MyHttpHandler();
+        HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
+        HttpContext hc = server.createContext("/", h);
+        hc.setAuthenticator(new MyServerAuthenticator(
+                proxy, scheme, principal, ktab));
+        server.start();
+        return server;
+    }
+
+    static class MyHttpHandler implements HttpHandler {
+        public void handle(HttpExchange t) throws IOException {
+            t.sendResponseHeaders(200, 0);
+            t.getResponseBody().write(CONTENT.getBytes());
+            t.close();
+        }
+    }
+
+    static class MyServerAuthenticator
+            extends com.sun.net.httpserver.Authenticator {
+        Subject s = new Subject();
+        GSSManager m = null;
+        GSSCredential cred = null;
+        String scheme = null;
+        String reqHdr = "WWW-Authenticate";
+        String respHdr = "Authorization";
+        int err = HttpURLConnection.HTTP_UNAUTHORIZED;
+
+        public MyServerAuthenticator(boolean proxy, String scheme,
+                String principal, String ktab) throws Exception {
+
+            this.scheme = scheme;
+            if (proxy) {
+                reqHdr = "Proxy-Authenticate";
+                respHdr = "Proxy-Authorization";
+                err = HttpURLConnection.HTTP_PROXY_AUTH;
+            }
+
+            Krb5LoginModule krb5 = new Krb5LoginModule();
+            Map<String, String> map = new HashMap<String, String>();
+            Map<String, Object> shared = new HashMap<String, Object>();
+
+            map.put("storeKey", "true");
+            map.put("isInitiator", "false");
+            map.put("useKeyTab", "true");
+            map.put("keyTab", ktab);
+            map.put("principal", principal);
+            krb5.initialize(s, null, shared, map);
+            krb5.login();
+            krb5.commit();
+            m = GSSManager.getInstance();
+            cred = Subject.doAs(s, new PrivilegedExceptionAction<GSSCredential>() {
+                @Override
+                public GSSCredential run() throws Exception {
+                    System.err.println("Creating GSSCredential");
+                    return m.createCredential(
+                            null,
+                            GSSCredential.INDEFINITE_LIFETIME,
+                            MyServerAuthenticator.this.scheme.equalsIgnoreCase("Negotiate")?
+                                    GSSUtil.GSS_SPNEGO_MECH_OID:
+                                    GSSUtil.GSS_KRB5_MECH_OID,
+                            GSSCredential.ACCEPT_ONLY);
+                }
+            });
+        }
+
+        @Override
+        public Result authenticate(HttpExchange exch) {
+            // The GSContext is stored in an HttpContext attribute named
+            // "GSSContext" and is created at the first request.
+            GSSContext c = null;
+            String auth = exch.getRequestHeaders().getFirst(respHdr);
+            try {
+                c = (GSSContext)exch.getHttpContext().getAttributes().get("GSSContext");
+                if (auth == null) {                 // First request
+                    Headers map = exch.getResponseHeaders();
+                    map.set (reqHdr, scheme);        // Challenge!
+                    c = Subject.doAs(s, new PrivilegedExceptionAction<GSSContext>() {
+                        @Override
+                        public GSSContext run() throws Exception {
+                            return m.createContext(cred);
+                        }
+                    });
+                    exch.getHttpContext().getAttributes().put("GSSContext", c);
+                    return new com.sun.net.httpserver.Authenticator.Retry(err);
+                } else {                            // Later requests
+                    byte[] token = new sun.misc.BASE64Decoder()
+                            .decodeBuffer(auth.split(" ")[1]);
+                    token = c.acceptSecContext(token, 0, token.length);
+                    Headers map = exch.getResponseHeaders();
+                    map.set (reqHdr, scheme + " " + new sun.misc.BASE64Encoder()
+                            .encode(token).replaceAll("\\s", ""));
+                    if (c.isEstablished()) {
+                        return new com.sun.net.httpserver.Authenticator.Success(
+                                new HttpPrincipal(c.getSrcName().toString(), ""));
+                    } else {
+                        return new com.sun.net.httpserver.Authenticator.Retry(err);
+                    }
+                }
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    @Override
+    public NameService createNameService() throws Exception {
+        NameService ns = new NameService() {
+            @Override
+            public InetAddress[] lookupAllHostAddr(String host)
+                    throws UnknownHostException {
+                // Everything is localhost
+                return new InetAddress[]{
+                    InetAddress.getByAddress(host, new byte[]{127,0,0,1})
+                };
+            }
+            @Override
+            public String getHostByAddr(byte[] addr)
+                    throws UnknownHostException {
+                // No reverse lookup
+                throw new UnknownHostException();
+            }
+        };
+        return ns;
+    }
+
+    @Override
+    public String getProviderName() {
+        return "mock";
+    }
+
+    @Override
+    public String getType() {
+        return "ns";
+    }
+}
+