undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
This commit is contained in:
commit
0a580b313d
70422 changed files with 17213626 additions and 0 deletions
517
test/jdk/sun/security/krb5/auto/HttpNegotiateServer.java
Normal file
517
test/jdk/sun/security/krb5/auto/HttpNegotiateServer.java
Normal file
|
|
@ -0,0 +1,517 @@
|
|||
/*
|
||||
* Copyright (c) 2009, 2024, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 6578647 6829283 8171340 8194486
|
||||
* @modules java.base/sun.security.util
|
||||
* java.security.jgss/sun.security.krb5.internal:+open
|
||||
* java.security.jgss/sun.security.jgss
|
||||
* java.security.jgss/sun.security.krb5:+open
|
||||
* java.security.jgss/sun.security.jgss.krb5
|
||||
* java.security.jgss/sun.security.krb5.internal.ccache
|
||||
* java.security.jgss/sun.security.krb5.internal.crypto
|
||||
* java.security.jgss/sun.security.krb5.internal.ktab
|
||||
* jdk.security.auth
|
||||
* jdk.security.jgss
|
||||
* jdk.httpserver
|
||||
* @summary Undefined requesting URL in java.net.Authenticator
|
||||
* .getPasswordAuthentication()
|
||||
* @summary HTTP/Negotiate: Authenticator triggered again when
|
||||
* user cancels the first one
|
||||
* @library /test/lib
|
||||
* @run main jdk.test.lib.FileInstaller TestHosts TestHosts
|
||||
* @run main/othervm -Djdk.net.hosts.file=TestHosts 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.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.PasswordAuthentication;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.security.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.security.auth.Subject;
|
||||
import javax.security.auth.callback.Callback;
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
import javax.security.auth.callback.NameCallback;
|
||||
import javax.security.auth.callback.PasswordCallback;
|
||||
import javax.security.auth.callback.UnsupportedCallbackException;
|
||||
import javax.security.auth.login.AppConfigurationEntry;
|
||||
import javax.security.auth.login.Configuration;
|
||||
import javax.security.auth.login.LoginContext;
|
||||
import javax.security.auth.login.LoginException;
|
||||
import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
|
||||
import org.ietf.jgss.GSSContext;
|
||||
import org.ietf.jgss.GSSCredential;
|
||||
import org.ietf.jgss.GSSManager;
|
||||
import sun.security.jgss.GSSUtil;
|
||||
import sun.security.krb5.Config;
|
||||
import java.util.Base64;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletionException;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
// 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 String WEB_HOST = "host.web.domain";
|
||||
final static String PROXY_HOST = "host.proxy.domain";
|
||||
|
||||
// web page content
|
||||
final static String CONTENT = "Hello, World!";
|
||||
|
||||
// For 6829283, count how many times the Authenticator is called.
|
||||
static int count = 0;
|
||||
|
||||
static int webPort, proxyPort;
|
||||
|
||||
// URLs for web test, proxy test. The proxy server is not a real proxy
|
||||
// since it fakes the same content for any URL. :)
|
||||
static URL webUrl, proxyUrl;
|
||||
|
||||
/**
|
||||
* 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() != proxyPort) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This Authenticator knows nothing
|
||||
*/
|
||||
static class KnowNothingAuthenticator extends java.net.Authenticator {
|
||||
@Override
|
||||
public PasswordAuthentication getPasswordAuthentication () {
|
||||
HttpNegotiateServer.count++;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
throws Exception {
|
||||
|
||||
System.setProperty("sun.security.krb5.debug", "true");
|
||||
|
||||
KDC kdcw = KDC.create(REALM_WEB);
|
||||
kdcw.addPrincipal(WEB_USER, WEB_PASS);
|
||||
kdcw.addPrincipalRandKey("krbtgt/" + REALM_WEB);
|
||||
kdcw.addPrincipalRandKey("HTTP/" + WEB_HOST);
|
||||
|
||||
KDC kdcp = KDC.create(REALM_PROXY);
|
||||
kdcp.addPrincipal(PROXY_USER, PROXY_PASS);
|
||||
kdcp.addPrincipalRandKey("krbtgt/" + REALM_PROXY);
|
||||
kdcp.addPrincipalRandKey("HTTP/" + PROXY_HOST);
|
||||
|
||||
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();
|
||||
KDC.writeMultiKtab(KRB5_TAB, kdcw, kdcp);
|
||||
|
||||
// Write a customized JAAS conf file, so that any kinit cache
|
||||
// will be ignored.
|
||||
System.setProperty("java.security.auth.login.config", OneKDC.JAAS_CONF);
|
||||
File f = new File(OneKDC.JAAS_CONF);
|
||||
FileOutputStream fos = new FileOutputStream(f);
|
||||
fos.write((
|
||||
"com.sun.security.jgss.krb5.initiate {\n" +
|
||||
" com.sun.security.auth.module.Krb5LoginModule required;\n};\n"
|
||||
).getBytes());
|
||||
fos.close();
|
||||
|
||||
HttpServer h1 = httpd("Negotiate", false,
|
||||
"HTTP/" + WEB_HOST + "@" + REALM_WEB, KRB5_TAB);
|
||||
webPort = h1.getAddress().getPort();
|
||||
HttpServer h2 = httpd("Negotiate", true,
|
||||
"HTTP/" + PROXY_HOST + "@" + REALM_PROXY, KRB5_TAB);
|
||||
proxyPort = h2.getAddress().getPort();
|
||||
|
||||
webUrl = new URL("http://" + WEB_HOST +":" + webPort + "/a/b/c");
|
||||
proxyUrl = new URL("http://nosuchplace/a/b/c");
|
||||
|
||||
try {
|
||||
Exception e1 = null, e2 = null, e3 = null;
|
||||
try {
|
||||
test6578647();
|
||||
} catch (Exception e) {
|
||||
e1 = e;
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
test6829283();
|
||||
} catch (Exception e) {
|
||||
e2 = e;
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
test8077155();
|
||||
} catch (Exception e) {
|
||||
e3 = e;
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (e1 != null || e2 != null || e3 != null) {
|
||||
throw new RuntimeException("Test error");
|
||||
}
|
||||
} finally {
|
||||
// Must stop. Seems there's no HttpServer.startAsDaemon()
|
||||
if (h1 != null) h1.stop(0);
|
||||
if (h2 != null) h2.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
static void test6578647() throws Exception {
|
||||
BufferedReader reader;
|
||||
java.net.Authenticator.setDefault(new KnowAllAuthenticator());
|
||||
|
||||
reader = new BufferedReader(new InputStreamReader(
|
||||
webUrl.openConnection(Proxy.NO_PROXY).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, proxyPort)))
|
||||
.getInputStream()));
|
||||
if (!reader.readLine().equals(CONTENT)) {
|
||||
throw new RuntimeException("Bad content");
|
||||
}
|
||||
}
|
||||
|
||||
static void test6829283() throws Exception {
|
||||
BufferedReader reader;
|
||||
java.net.Authenticator.setDefault(new KnowNothingAuthenticator());
|
||||
try {
|
||||
new BufferedReader(new InputStreamReader(
|
||||
webUrl.openConnection(Proxy.NO_PROXY).getInputStream()));
|
||||
} catch (IOException ioe) {
|
||||
// Will fail since no username and password is provided.
|
||||
}
|
||||
if (count > 1) {
|
||||
throw new RuntimeException("Authenticator called twice");
|
||||
}
|
||||
}
|
||||
|
||||
static void testConnect() {
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
URL url = webUrl;
|
||||
|
||||
URLConnection conn = url.openConnection(Proxy.NO_PROXY);
|
||||
conn.connect();
|
||||
inputStream = conn.getInputStream();
|
||||
byte[] b = new byte[inputStream.available()];
|
||||
for (int j = 0; j < b.length; j++) {
|
||||
b[j] = (byte) inputStream.read();
|
||||
}
|
||||
String s = new String(b);
|
||||
System.out.println("Length: " + s.length());
|
||||
System.out.println(s);
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
} finally {
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void test8077155() throws Exception {
|
||||
final String username = WEB_USER;
|
||||
final char[] password = WEB_PASS;
|
||||
|
||||
CallbackHandler callback = new CallbackHandler() {
|
||||
@Override
|
||||
public void handle(Callback[] pCallbacks)
|
||||
throws IOException, UnsupportedCallbackException {
|
||||
for (Callback cb : pCallbacks) {
|
||||
if (cb instanceof NameCallback) {
|
||||
NameCallback ncb = (NameCallback)cb;
|
||||
ncb.setName(username);
|
||||
|
||||
} else if (cb instanceof PasswordCallback) {
|
||||
PasswordCallback pwdcb = (PasswordCallback) cb;
|
||||
pwdcb.setPassword(password);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
final String jaasConfigName = "oracle.test.kerberos.login";
|
||||
final String krb5LoginModule
|
||||
= "com.sun.security.auth.module.Krb5LoginModule";
|
||||
|
||||
Configuration loginConfig = new Configuration() {
|
||||
@Override
|
||||
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
|
||||
if (! jaasConfigName.equals(name)) {
|
||||
return new AppConfigurationEntry[0];
|
||||
}
|
||||
|
||||
Map<String, String> options = new HashMap<String, String>();
|
||||
options.put("useTicketCache", Boolean.FALSE.toString());
|
||||
options.put("useKeyTab", Boolean.FALSE.toString());
|
||||
|
||||
return new AppConfigurationEntry[] {
|
||||
new AppConfigurationEntry(krb5LoginModule,
|
||||
LoginModuleControlFlag.REQUIRED,
|
||||
options)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// oracle context/subject/login
|
||||
LoginContext context = null;
|
||||
try {
|
||||
context = new LoginContext(
|
||||
"oracle.test.kerberos.login", null, callback, loginConfig);
|
||||
context.login();
|
||||
|
||||
} catch (LoginException ex) {
|
||||
ex.printStackTrace();
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
|
||||
Subject subject = context.getSubject();
|
||||
|
||||
final Callable<Object> test_action
|
||||
= new Callable<Object>() {
|
||||
public Object call() throws Exception {
|
||||
testConnect();
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
System.err.println("\n\nExpecting to succeed when executing " +
|
||||
"with the logged in subject.");
|
||||
|
||||
try {
|
||||
Subject.callAs(subject, test_action);
|
||||
System.err.println("\n\nConnection succeed when executing " +
|
||||
"with the logged in subject.");
|
||||
} catch (CompletionException e) {
|
||||
System.err.println("\n\nFailure unexpected when executing " +
|
||||
"with the logged in subject.");
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("Failed to login as subject");
|
||||
}
|
||||
|
||||
try {
|
||||
System.err.println("\n\nExpecting to fail when running " +
|
||||
"with the current user's login.");
|
||||
testConnect();
|
||||
} catch (Exception ex) {
|
||||
System.err.println("\nConnect failed when running " +
|
||||
"with the current user's login:\n" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(String scheme, boolean proxy,
|
||||
String principal, String ktab) throws Exception {
|
||||
MyHttpHandler h = new MyHttpHandler();
|
||||
HttpServer server = HttpServer.create(new InetSocketAddress(0), 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<>();
|
||||
Map<String, Object> shared = new HashMap<>();
|
||||
|
||||
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.callAs(s, new Callable<GSSCredential>() {
|
||||
@Override
|
||||
public GSSCredential call() 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.callAs(s, () -> m.createContext(cred));
|
||||
exch.getHttpContext().getAttributes().put("GSSContext", c);
|
||||
return new com.sun.net.httpserver.Authenticator.Retry(err);
|
||||
} else { // Later requests
|
||||
byte[] token = Base64.getMimeDecoder()
|
||||
.decode(auth.split(" ")[1]);
|
||||
token = c.acceptSecContext(token, 0, token.length);
|
||||
Headers map = exch.getResponseHeaders();
|
||||
map.set (reqHdr, scheme + " " + Base64.getMimeEncoder()
|
||||
.encodeToString(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue