1 /* 2 * Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @bug 6578647 6829283 27 * @run main/othervm HttpNegotiateServer 28 * @summary Undefined requesting URL in java.net.Authenticator.getPasswordAuthentication() 29 * @summary HTTP/Negotiate: Authenticator triggered again when user cancels the first one 30 */ 31 32 import com.sun.net.httpserver.Headers; 33 import com.sun.net.httpserver.HttpContext; 34 import com.sun.net.httpserver.HttpExchange; 35 import com.sun.net.httpserver.HttpHandler; 36 import com.sun.net.httpserver.HttpServer; 37 import com.sun.net.httpserver.HttpPrincipal; 38 import com.sun.security.auth.module.Krb5LoginModule; 39 import java.io.BufferedReader; 40 import java.io.File; 41 import java.io.FileOutputStream; 42 import java.io.IOException; 43 import java.io.InputStreamReader; 44 import java.net.HttpURLConnection; 45 import java.net.InetSocketAddress; 46 import java.net.PasswordAuthentication; 47 import java.net.Proxy; 48 import java.net.URL; 49 import java.security.PrivilegedExceptionAction; 50 import java.util.HashMap; 51 import java.util.Map; 52 import javax.security.auth.Subject; 53 import org.ietf.jgss.GSSContext; 54 import org.ietf.jgss.GSSCredential; 55 import org.ietf.jgss.GSSManager; 56 import sun.security.jgss.GSSUtil; 57 import sun.security.krb5.Config; 58 59 /** 60 * Basic JGSS/krb5 test with 3 parties: client, server, backend server. Each 61 * party uses JAAS login to get subjects and executes JGSS calls using 62 * Subject.doAs. 63 */ 64 public class HttpNegotiateServer { 65 66 // Two realm, web server in one, proxy server in another 67 final static String REALM_WEB = "WEB.DOMAIN"; 68 final static String REALM_PROXY = "PROXY.DOMAIN"; 69 final static String KRB5_CONF = "web.conf"; 70 final static String KRB5_TAB = "web.ktab"; 71 72 // user principals 73 final static String WEB_USER = "web"; 74 final static char[] WEB_PASS = "webby".toCharArray(); 75 final static String PROXY_USER = "pro"; 76 final static char[] PROXY_PASS = "proxy".toCharArray(); 77 78 79 final static String WEB_HOST = "host.web.domain"; 80 final static String PROXY_HOST = "host.proxy.domain"; 81 82 // web page content 83 final static String CONTENT = "Hello, World!"; 84 85 // For 6829283, count how many times the Authenticator is called. 86 static int count = 0; 87 88 static int webPort, proxyPort; 89 90 // URLs for web test, proxy test. The proxy server is not a real proxy 91 // since it fakes the same content for any URL. :) 92 static URL webUrl, proxyUrl; 93 94 /** 95 * This Authenticator checks everything: 96 * scheme, protocol, requestor type, host, port, and url 97 */ 98 static class KnowAllAuthenticator extends java.net.Authenticator { 99 public PasswordAuthentication getPasswordAuthentication () { 100 if (!getRequestingScheme().equalsIgnoreCase("Negotiate")) { 101 throw new RuntimeException("Bad scheme"); 102 } 103 if (!getRequestingProtocol().equalsIgnoreCase("HTTP")) { 104 throw new RuntimeException("Bad protocol"); 105 } 106 if (getRequestorType() == RequestorType.SERVER) { 107 if (!this.getRequestingHost().equalsIgnoreCase(webUrl.getHost())) { 108 throw new RuntimeException("Bad host"); 109 } 110 if (this.getRequestingPort() != webUrl.getPort()) { 111 throw new RuntimeException("Bad port"); 112 } 113 if (!this.getRequestingURL().equals(webUrl)) { 114 throw new RuntimeException("Bad url"); 115 } 116 return new PasswordAuthentication( 117 WEB_USER+"@"+REALM_WEB, WEB_PASS); 118 } else if (getRequestorType() == RequestorType.PROXY) { 119 if (!this.getRequestingHost().equalsIgnoreCase(PROXY_HOST)) { 120 throw new RuntimeException("Bad host"); 121 } 122 if (this.getRequestingPort() != proxyPort) { 123 throw new RuntimeException("Bad port"); 124 } 125 if (!this.getRequestingURL().equals(proxyUrl)) { 126 throw new RuntimeException("Bad url"); 127 } 128 return new PasswordAuthentication( 129 PROXY_USER+"@"+REALM_PROXY, PROXY_PASS); 130 } else { 131 throw new RuntimeException("Bad requster type"); 132 } 133 } 134 } 135 136 /** 137 * This Authenticator knows nothing 138 */ 139 static class KnowNothingAuthenticator extends java.net.Authenticator { 140 @Override 141 public PasswordAuthentication getPasswordAuthentication () { 142 HttpNegotiateServer.count++; 143 return null; 144 } 145 } 146 147 public static void main(String[] args) 148 throws Exception { 149 150 KDC kdcw = KDC.create(REALM_WEB); 151 kdcw.addPrincipal(WEB_USER, WEB_PASS); 152 kdcw.addPrincipalRandKey("krbtgt/" + REALM_WEB); 153 kdcw.addPrincipalRandKey("HTTP/" + WEB_HOST); 154 155 KDC kdcp = KDC.create(REALM_PROXY); 156 kdcp.addPrincipal(PROXY_USER, PROXY_PASS); 157 kdcp.addPrincipalRandKey("krbtgt/" + REALM_PROXY); 158 kdcp.addPrincipalRandKey("HTTP/" + PROXY_HOST); 159 160 KDC.saveConfig(KRB5_CONF, kdcw, kdcp, 161 "default_keytab_name = " + KRB5_TAB, 162 "[domain_realm]", 163 "", 164 ".web.domain="+REALM_WEB, 165 ".proxy.domain="+REALM_PROXY); 166 167 System.setProperty("java.security.krb5.conf", KRB5_CONF); 168 Config.refresh(); 169 KDC.writeMultiKtab(KRB5_TAB, kdcw, kdcp); 170 171 // Write a customized JAAS conf file, so that any kinit cache 172 // will be ignored. 173 System.setProperty("java.security.auth.login.config", OneKDC.JAAS_CONF); 174 File f = new File(OneKDC.JAAS_CONF); 175 FileOutputStream fos = new FileOutputStream(f); 176 fos.write(( 177 "com.sun.security.jgss.krb5.initiate {\n" + 178 " com.sun.security.auth.module.Krb5LoginModule required;\n};\n" 179 ).getBytes()); 180 fos.close(); 181 182 HttpServer h1 = httpd("Negotiate", false, 183 "HTTP/" + WEB_HOST + "@" + REALM_WEB, KRB5_TAB); 184 webPort = h1.getAddress().getPort(); 185 HttpServer h2 = httpd("Negotiate", true, 186 "HTTP/" + PROXY_HOST + "@" + REALM_PROXY, KRB5_TAB); 187 proxyPort = h2.getAddress().getPort(); 188 189 webUrl = new URL("http://" + WEB_HOST +":" + webPort + "/a/b/c"); 190 proxyUrl = new URL("http://nosuchplace/a/b/c"); 191 192 try { 193 Exception e1 = null, e2 = null; 194 try { 195 test6578647(); 196 } catch (Exception e) { 197 e1 = e; 198 e.printStackTrace(); 199 } 200 try { 201 test6829283(); 202 } catch (Exception e) { 203 e2 = e; 204 e.printStackTrace(); 205 } 206 if (e1 != null || e2 != null) { 207 throw new RuntimeException("Test error"); 208 } 209 } finally { 210 // Must stop. Seems there's no HttpServer.startAsDaemon() 211 if (h1 != null) h1.stop(0); 212 if (h2 != null) h2.stop(0); 213 } 214 } 215 216 static void test6578647() throws Exception { 217 BufferedReader reader; 218 java.net.Authenticator.setDefault(new KnowAllAuthenticator()); 219 220 reader = new BufferedReader(new InputStreamReader( 221 webUrl.openConnection().getInputStream())); 222 if (!reader.readLine().equals(CONTENT)) { 223 throw new RuntimeException("Bad content"); 224 } 225 226 reader = new BufferedReader(new InputStreamReader( 227 proxyUrl.openConnection( 228 new Proxy(Proxy.Type.HTTP, 229 new InetSocketAddress(PROXY_HOST, proxyPort))) 230 .getInputStream())); 231 if (!reader.readLine().equals(CONTENT)) { 232 throw new RuntimeException("Bad content"); 233 } 234 } 235 236 static void test6829283() throws Exception { 237 BufferedReader reader; 238 java.net.Authenticator.setDefault(new KnowNothingAuthenticator()); 239 try { 240 new BufferedReader(new InputStreamReader( 241 webUrl.openConnection().getInputStream())); 242 } catch (IOException ioe) { 243 // Will fail since no username and password is provided. 244 } 245 if (count > 1) { 246 throw new RuntimeException("Authenticator called twice"); 247 } 248 } 249 250 /** 251 * Creates and starts an HTTP or proxy server that requires 252 * Negotiate authentication. 253 * @param scheme "Negotiate" or "Kerberos" 254 * @param principal the krb5 service principal the server runs with 255 * @return the server 256 */ 257 public static HttpServer httpd(String scheme, boolean proxy, 258 String principal, String ktab) throws Exception { 259 MyHttpHandler h = new MyHttpHandler(); 260 HttpServer server = HttpServer.create(new InetSocketAddress(0), 0); 261 HttpContext hc = server.createContext("/", h); 262 hc.setAuthenticator(new MyServerAuthenticator( 263 proxy, scheme, principal, ktab)); 264 server.start(); 265 return server; 266 } 267 268 static class MyHttpHandler implements HttpHandler { 269 public void handle(HttpExchange t) throws IOException { 270 t.sendResponseHeaders(200, 0); 271 t.getResponseBody().write(CONTENT.getBytes()); 272 t.close(); 273 } 274 } 275 276 static class MyServerAuthenticator 277 extends com.sun.net.httpserver.Authenticator { 278 Subject s = new Subject(); 279 GSSManager m = null; 280 GSSCredential cred = null; 281 String scheme = null; 282 String reqHdr = "WWW-Authenticate"; 283 String respHdr = "Authorization"; 284 int err = HttpURLConnection.HTTP_UNAUTHORIZED; 285 286 public MyServerAuthenticator(boolean proxy, String scheme, 287 String principal, String ktab) throws Exception { 288 289 this.scheme = scheme; 290 if (proxy) { 291 reqHdr = "Proxy-Authenticate"; 292 respHdr = "Proxy-Authorization"; 293 err = HttpURLConnection.HTTP_PROXY_AUTH; 294 } 295 296 Krb5LoginModule krb5 = new Krb5LoginModule(); 297 Map<String, String> map = new HashMap<>(); 298 Map<String, Object> shared = new HashMap<>(); 299 300 map.put("storeKey", "true"); 301 map.put("isInitiator", "false"); 302 map.put("useKeyTab", "true"); 303 map.put("keyTab", ktab); 304 map.put("principal", principal); 305 krb5.initialize(s, null, shared, map); 306 krb5.login(); 307 krb5.commit(); 308 m = GSSManager.getInstance(); 309 cred = Subject.doAs(s, new PrivilegedExceptionAction<GSSCredential>() { 310 @Override 311 public GSSCredential run() throws Exception { 312 System.err.println("Creating GSSCredential"); 313 return m.createCredential( 314 null, 315 GSSCredential.INDEFINITE_LIFETIME, 316 MyServerAuthenticator.this.scheme.equalsIgnoreCase("Negotiate")? 317 GSSUtil.GSS_SPNEGO_MECH_OID: 318 GSSUtil.GSS_KRB5_MECH_OID, 319 GSSCredential.ACCEPT_ONLY); 320 } 321 }); 322 } 323 324 @Override 325 public Result authenticate(HttpExchange exch) { 326 // The GSContext is stored in an HttpContext attribute named 327 // "GSSContext" and is created at the first request. 328 GSSContext c = null; 329 String auth = exch.getRequestHeaders().getFirst(respHdr); 330 try { 331 c = (GSSContext)exch.getHttpContext().getAttributes().get("GSSContext"); 332 if (auth == null) { // First request 333 Headers map = exch.getResponseHeaders(); 334 map.set (reqHdr, scheme); // Challenge! 335 c = Subject.doAs(s, new PrivilegedExceptionAction<GSSContext>() { 336 @Override 337 public GSSContext run() throws Exception { 338 return m.createContext(cred); 339 } 340 }); 341 exch.getHttpContext().getAttributes().put("GSSContext", c); 342 return new com.sun.net.httpserver.Authenticator.Retry(err); 343 } else { // Later requests 344 byte[] token = new sun.misc.BASE64Decoder() 345 .decodeBuffer(auth.split(" ")[1]); 346 token = c.acceptSecContext(token, 0, token.length); 347 Headers map = exch.getResponseHeaders(); 348 map.set (reqHdr, scheme + " " + new sun.misc.BASE64Encoder() 349 .encode(token).replaceAll("\\s", "")); 350 if (c.isEstablished()) { 351 return new com.sun.net.httpserver.Authenticator.Success( 352 new HttpPrincipal(c.getSrcName().toString(), "")); 353 } else { 354 return new com.sun.net.httpserver.Authenticator.Retry(err); 355 } 356 } 357 } catch (Exception e) { 358 throw new RuntimeException(e); 359 } 360 } 361 } 362 }