Path: blob/master/test/jdk/sun/security/krb5/auto/HttpsCB.java
66646 views
/*1* Copyright (c) 2022, 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 8279842 828229326* @modules java.base/sun.security.util27* java.security.jgss/sun.security.jgss28* java.security.jgss/sun.security.jgss.krb529* java.security.jgss/sun.security.jgss.krb5.internal30* java.security.jgss/sun.security.krb5.internal:+open31* java.security.jgss/sun.security.krb5:+open32* java.security.jgss/sun.security.krb5.internal.ccache33* java.security.jgss/sun.security.krb5.internal.crypto34* java.security.jgss/sun.security.krb5.internal.ktab35* jdk.security.auth36* jdk.security.jgss37* jdk.httpserver38* @summary HTTPS Channel Binding support for Java GSS/Kerberos39* @library /test/lib40* @run main jdk.test.lib.FileInstaller TestHosts TestHosts41* @run main/othervm -Djdk.net.hosts.file=TestHosts42* -Djdk.https.negotiate.cbt=always HttpsCB true true43* @run main/othervm -Djdk.net.hosts.file=TestHosts44* -Djdk.https.negotiate.cbt=never HttpsCB false true45* @run main/othervm -Djdk.net.hosts.file=TestHosts46* -Djdk.https.negotiate.cbt=invalid HttpsCB false true47* @run main/othervm -Djdk.net.hosts.file=TestHosts48* HttpsCB false true49* @run main/othervm -Djdk.net.hosts.file=TestHosts50* -Djdk.https.negotiate.cbt=domain:other.com HttpsCB false true51* @run main/othervm -Djdk.net.hosts.file=TestHosts52* -Djdk.https.negotiate.cbt=domain:host.web.domain HttpsCB true true53* @run main/othervm -Djdk.net.hosts.file=TestHosts54* -Djdk.https.negotiate.cbt=domain:HOST.WEB.DOMAIN HttpsCB true true55* @run main/othervm -Djdk.net.hosts.file=TestHosts56* -Djdk.https.negotiate.cbt=domain:*.web.domain HttpsCB true true57* @run main/othervm -Djdk.net.hosts.file=TestHosts58* -Djdk.https.negotiate.cbt=domain:*.WEB.Domain HttpsCB true true59* @run main/othervm -Djdk.net.hosts.file=TestHosts60* -Djdk.https.negotiate.cbt=domain:*.Invalid,*.WEB.Domain HttpsCB true true61*/6263import com.sun.net.httpserver.Headers;64import com.sun.net.httpserver.HttpExchange;65import com.sun.net.httpserver.HttpHandler;66import com.sun.net.httpserver.HttpServer;67import com.sun.net.httpserver.HttpPrincipal;68import com.sun.net.httpserver.HttpsConfigurator;69import com.sun.net.httpserver.HttpsExchange;70import com.sun.net.httpserver.HttpsServer;71import com.sun.security.auth.module.Krb5LoginModule;72import java.io.BufferedReader;73import java.io.File;74import java.io.FileOutputStream;75import java.io.IOException;76import java.io.InputStreamReader;77import java.net.HttpURLConnection;78import java.net.InetSocketAddress;79import java.net.PasswordAuthentication;80import java.net.Proxy;81import java.net.Socket;82import java.net.URL;83import java.security.cert.X509Certificate;84import java.security.PrivilegedExceptionAction;85import java.util.HashMap;86import java.util.Map;87import javax.net.ssl.HttpsURLConnection;88import javax.net.ssl.SSLContext;89import javax.net.ssl.SSLEngine;90import javax.net.ssl.TrustManager;91import javax.net.ssl.X509ExtendedTrustManager;92import javax.security.auth.Subject;9394import jdk.test.lib.Asserts;95import jdk.test.lib.net.SimpleSSLContext;96import org.ietf.jgss.GSSContext;97import org.ietf.jgss.GSSCredential;98import org.ietf.jgss.GSSManager;99import sun.security.jgss.GSSUtil;100import sun.security.jgss.krb5.internal.TlsChannelBindingImpl;101import sun.security.krb5.Config;102import sun.security.util.TlsChannelBinding;103104import java.util.Base64;105import java.util.concurrent.Callable;106107public class HttpsCB {108109final static String REALM_WEB = "WEB.DOMAIN";110final static String KRB5_CONF = "web.conf";111final static String KRB5_TAB = "web.ktab";112113final static String WEB_USER = "web";114final static char[] WEB_PASS = "webby".toCharArray();115final static String WEB_HOST = "host.web.domain";116final static String CONTENT = "Hello, World!";117118static int webPort;119static URL cbtURL;120static URL normalURL;121122public static void main(String[] args)123throws Exception {124125boolean expectCBT = Boolean.parseBoolean(args[0]);126boolean expectNoCBT = Boolean.parseBoolean(args[1]);127128System.setProperty("sun.security.krb5.debug", "true");129130KDC kdcw = KDC.create(REALM_WEB);131kdcw.addPrincipal(WEB_USER, WEB_PASS);132kdcw.addPrincipalRandKey("krbtgt/" + REALM_WEB);133kdcw.addPrincipalRandKey("HTTP/" + WEB_HOST);134135KDC.saveConfig(KRB5_CONF, kdcw,136"default_keytab_name = " + KRB5_TAB,137"[domain_realm]",138"",139".web.domain="+REALM_WEB);140141System.setProperty("java.security.krb5.conf", KRB5_CONF);142Config.refresh();143KDC.writeMultiKtab(KRB5_TAB, kdcw);144145// Write a customized JAAS conf file, so that any kinit cache146// will be ignored.147System.setProperty("java.security.auth.login.config", OneKDC.JAAS_CONF);148File f = new File(OneKDC.JAAS_CONF);149FileOutputStream fos = new FileOutputStream(f);150fos.write((151"com.sun.security.jgss.krb5.initiate {\n" +152" com.sun.security.auth.module.Krb5LoginModule required;\n};\n"153).getBytes());154fos.close();155156HttpServer h1 = httpd("Negotiate",157"HTTP/" + WEB_HOST + "@" + REALM_WEB, KRB5_TAB);158webPort = h1.getAddress().getPort();159160cbtURL = new URL("https://" + WEB_HOST +":" + webPort + "/cbt");161normalURL = new URL("https://" + WEB_HOST +":" + webPort + "/normal");162163java.net.Authenticator.setDefault(new java.net.Authenticator() {164public PasswordAuthentication getPasswordAuthentication () {165return new PasswordAuthentication(166WEB_USER+"@"+REALM_WEB, WEB_PASS);167}168});169170// Client-side SSLContext needs to ignore hostname mismatch171// and untrusted certificate.172SSLContext sc = SSLContext.getInstance("SSL");173sc.init(null, new TrustManager[] {174new X509ExtendedTrustManager() {175public X509Certificate[] getAcceptedIssuers() {176return null;177}178public void checkClientTrusted(X509Certificate[] chain,179String authType, Socket socket) { }180public void checkServerTrusted(X509Certificate[] chain,181String authType, Socket socket) { }182public void checkClientTrusted(X509Certificate[] chain,183String authType, SSLEngine engine) { }184public void checkServerTrusted(X509Certificate[] chain,185String authType, SSLEngine engine) { }186public void checkClientTrusted(X509Certificate[] certs,187String authType) { }188public void checkServerTrusted(X509Certificate[] certs,189String authType) { }190}191}, null);192193Asserts.assertEQ(visit(sc, cbtURL), expectCBT);194Asserts.assertEQ(visit(sc, normalURL), expectNoCBT);195}196197static boolean visit(SSLContext sc, URL url) {198try {199HttpsURLConnection conn = (HttpsURLConnection)200url.openConnection(Proxy.NO_PROXY);201conn.setSSLSocketFactory(sc.getSocketFactory());202BufferedReader reader;203reader = new BufferedReader(new InputStreamReader(204conn.getInputStream()));205return reader.readLine().equals(CONTENT);206} catch (IOException e) {207e.printStackTrace(System.out);208return false;209}210}211212static HttpServer httpd(String scheme, String principal, String ktab)213throws Exception {214MyHttpHandler h = new MyHttpHandler();215HttpsServer server = HttpsServer.create(new InetSocketAddress(0), 0);216server.setHttpsConfigurator(217new HttpsConfigurator(new SimpleSSLContext().get()));218server.createContext("/", h).setAuthenticator(219new MyServerAuthenticator(scheme, principal, ktab));220server.start();221return server;222}223224static class MyHttpHandler implements HttpHandler {225public void handle(HttpExchange t) throws IOException {226t.sendResponseHeaders(200, 0);227t.getResponseBody().write(CONTENT.getBytes());228t.close();229}230}231232static class MyServerAuthenticator233extends com.sun.net.httpserver.Authenticator {234Subject s = new Subject();235GSSManager m;236GSSCredential cred;237String scheme = null;238String reqHdr = "WWW-Authenticate";239String respHdr = "Authorization";240int err = HttpURLConnection.HTTP_UNAUTHORIZED;241242public MyServerAuthenticator(String scheme,243String principal, String ktab) throws Exception {244245this.scheme = scheme;246Krb5LoginModule krb5 = new Krb5LoginModule();247Map<String, String> map = new HashMap<>();248Map<String, Object> shared = new HashMap<>();249250map.put("storeKey", "true");251map.put("isInitiator", "false");252map.put("useKeyTab", "true");253map.put("keyTab", ktab);254map.put("principal", principal);255krb5.initialize(s, null, shared, map);256krb5.login();257krb5.commit();258m = GSSManager.getInstance();259cred = Subject.doAs(s, new PrivilegedExceptionAction<GSSCredential>() {260@Override261public GSSCredential run() throws Exception {262System.err.println("Creating GSSCredential");263return m.createCredential(264null,265GSSCredential.INDEFINITE_LIFETIME,266MyServerAuthenticator.this.scheme267.equalsIgnoreCase("Negotiate") ?268GSSUtil.GSS_SPNEGO_MECH_OID :269GSSUtil.GSS_KRB5_MECH_OID,270GSSCredential.ACCEPT_ONLY);271}272});273}274275@Override276public Result authenticate(HttpExchange exch) {277// The GSContext is stored in an HttpContext attribute named278// "GSSContext" and is created at the first request.279GSSContext c = null;280String auth = exch.getRequestHeaders().getFirst(respHdr);281try {282c = (GSSContext)exch.getHttpContext()283.getAttributes().get("GSSContext");284if (auth == null) { // First request285Headers map = exch.getResponseHeaders();286map.set (reqHdr, scheme); // Challenge!287c = Subject.doAs(s, new PrivilegedExceptionAction<GSSContext>() {288@Override289public GSSContext run() throws Exception {290return m.createContext(cred);291}292});293// CBT is required for cbtURL294if (exch instanceof HttpsExchange sexch295&& exch.getRequestURI().toString().equals("/cbt")) {296TlsChannelBinding b = TlsChannelBinding.create(297(X509Certificate) sexch.getSSLSession()298.getLocalCertificates()[0]);299c.setChannelBinding(300new TlsChannelBindingImpl(b.getData()));301}302exch.getHttpContext().getAttributes().put("GSSContext", c);303return new com.sun.net.httpserver.Authenticator.Retry(err);304} else { // Later requests305byte[] token = Base64.getMimeDecoder()306.decode(auth.split(" ")[1]);307token = c.acceptSecContext(token, 0, token.length);308Headers map = exch.getResponseHeaders();309map.set (reqHdr, scheme + " " + Base64.getMimeEncoder()310.encodeToString(token).replaceAll("\\s", ""));311if (c.isEstablished()) {312return new com.sun.net.httpserver.Authenticator.Success(313new HttpPrincipal(c.getSrcName().toString(), ""));314} else {315return new com.sun.net.httpserver.Authenticator.Retry(err);316}317}318} catch (Exception e) {319throw new RuntimeException(e);320}321}322}323}324325326