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 import java.util.Base64; 59 60 /** 61 * Basic JGSS/krb5 test with 3 parties: client, server, backend server. Each 62 * party uses JAAS login to get subjects and executes JGSS calls using 63 * Subject.doAs. 64 */ 65 public class HttpNegotiateServer { 66 67 // Two realm, web server in one, proxy server in another 68 final static String REALM_WEB = "WEB.DOMAIN"; 69 final static String REALM_PROXY = "PROXY.DOMAIN"; 70 final static String KRB5_CONF = "web.conf"; 71 final static String KRB5_TAB = "web.ktab"; 72 73 // user principals 74 final static String WEB_USER = "web"; 75 final static char[] WEB_PASS = "webby".toCharArray(); 76 final static String PROXY_USER = "pro"; 77 final static char[] PROXY_PASS = "proxy".toCharArray(); 78 79 80 final static String WEB_HOST = "host.web.domain"; 81 final static String PROXY_HOST = "host.proxy.domain"; 82 83 // web page content 84 final static String CONTENT = "Hello, World!"; 85 86 // For 6829283, count how many times the Authenticator is called. 87 static int count = 0; 88 89 static int webPort, proxyPort; 90 91 // URLs for web test, proxy test. The proxy server is not a real proxy 92 // since it fakes the same content for any URL. :) 93 static URL webUrl, proxyUrl; 94 95 /** 96 * This Authenticator checks everything: 97 * scheme, protocol, requestor type, host, port, and url 98 */ 99 static class KnowAllAuthenticator extends java.net.Authenticator { 100 public PasswordAuthentication getPasswordAuthentication () { 101 if (!getRequestingScheme().equalsIgnoreCase("Negotiate")) { 102 throw new RuntimeException("Bad scheme"); 103 } 104 if (!getRequestingProtocol().equalsIgnoreCase("HTTP")) { 105 throw new RuntimeException("Bad protocol"); 106 } 107 if (getRequestorType() == RequestorType.SERVER) { 108 if (!this.getRequestingHost().equalsIgnoreCase(webUrl.getHost())) { 109 throw new RuntimeException("Bad host"); 110 } 111 if (this.getRequestingPort() != webUrl.getPort()) { 112 throw new RuntimeException("Bad port"); 113 } 114 if (!this.getRequestingURL().equals(webUrl)) { 115 throw new RuntimeException("Bad url"); 116 } 117 return new PasswordAuthentication( 118 WEB_USER+"@"+REALM_WEB, WEB_PASS); 119 } else if (getRequestorType() == RequestorType.PROXY) { 120 if (!this.getRequestingHost().equalsIgnoreCase(PROXY_HOST)) { 121 throw new RuntimeException("Bad host"); 122 } 123 if (this.getRequestingPort() != proxyPort) { 124 throw new RuntimeException("Bad port"); 125 } 126 if (!this.getRequestingURL().equals(proxyUrl)) { 127 throw new RuntimeException("Bad url"); 128 } 129 return new PasswordAuthentication( 130 PROXY_USER+"@"+REALM_PROXY, PROXY_PASS); 131 } else { 132 throw new RuntimeException("Bad requster type"); 133 } 134 } 135 } 136 137 /** 138 * This Authenticator knows nothing 139 */ 140 static class KnowNothingAuthenticator extends java.net.Authenticator { 141 @Override 142 public PasswordAuthentication getPasswordAuthentication () { 143 HttpNegotiateServer.count++; 144 return null; 145 } 146 } 147 148 public static void main(String[] args) 149 throws Exception { 150 151 KDC kdcw = KDC.create(REALM_WEB); 152 kdcw.addPrincipal(WEB_USER, WEB_PASS); 153 kdcw.addPrincipalRandKey("krbtgt/" + REALM_WEB); 154 kdcw.addPrincipalRandKey("HTTP/" + WEB_HOST); 155 156 KDC kdcp = KDC.create(REALM_PROXY); 157 kdcp.addPrincipal(PROXY_USER, PROXY_PASS); 158 kdcp.addPrincipalRandKey("krbtgt/" + REALM_PROXY); 159 kdcp.addPrincipalRandKey("HTTP/" + PROXY_HOST); 160 161 KDC.saveConfig(KRB5_CONF, kdcw, kdcp, 162 "default_keytab_name = " + KRB5_TAB, 163 "[domain_realm]", 164 "", 165 ".web.domain="+REALM_WEB, 166 ".proxy.domain="+REALM_PROXY); 167 168 System.setProperty("java.security.krb5.conf", KRB5_CONF); 169 Config.refresh(); 170 KDC.writeMultiKtab(KRB5_TAB, kdcw, kdcp); 171 172 // Write a customized JAAS conf file, so that any kinit cache 173 // will be ignored. 174 System.setProperty("java.security.auth.login.config", OneKDC.JAAS_CONF); 175 File f = new File(OneKDC.JAAS_CONF); 176 FileOutputStream fos = new FileOutputStream(f); 177 fos.write(( 178 "com.sun.security.jgss.krb5.initiate {\n" + 179 " com.sun.security.auth.module.Krb5LoginModule required;\n};\n" 180 ).getBytes()); 181 fos.close(); 182 183 HttpServer h1 = httpd("Negotiate", false, 184 "HTTP/" + WEB_HOST + "@" + REALM_WEB, KRB5_TAB); 185 webPort = h1.getAddress().getPort(); 186 HttpServer h2 = httpd("Negotiate", true, 187 "HTTP/" + PROXY_HOST + "@" + REALM_PROXY, KRB5_TAB); 188 proxyPort = h2.getAddress().getPort(); 189 190 webUrl = new URL("http://" + WEB_HOST +":" + webPort + "/a/b/c"); 191 proxyUrl = new URL("http://nosuchplace/a/b/c"); 192 193 try { 194 Exception e1 = null, e2 = null; 195 try { 196 test6578647(); 197 } catch (Exception e) { 198 e1 = e; 199 e.printStackTrace(); 200 } 201 try { 202 test6829283(); 203 } catch (Exception e) { 204 e2 = e; 205 e.printStackTrace(); 206 } 207 if (e1 != null || e2 != null) { 208 throw new RuntimeException("Test error"); 209 } 210 } finally { 211 // Must stop. Seems there's no HttpServer.startAsDaemon() 212 if (h1 != null) h1.stop(0); 213 if (h2 != null) h2.stop(0); 214 } 215 } 216 217 static void test6578647() throws Exception { 218 BufferedReader reader; 219 java.net.Authenticator.setDefault(new KnowAllAuthenticator()); 220 221 reader = new BufferedReader(new InputStreamReader( 222 webUrl.openConnection().getInputStream())); 223 if (!reader.readLine().equals(CONTENT)) { 224 throw new RuntimeException("Bad content"); 225 } 226 227 reader = new BufferedReader(new InputStreamReader( 228 proxyUrl.openConnection( 229 new Proxy(Proxy.Type.HTTP, 230 new InetSocketAddress(PROXY_HOST, proxyPort))) 231 .getInputStream())); 232 if (!reader.readLine().equals(CONTENT)) { 233 throw new RuntimeException("Bad content"); 234 } 235 } 236 237 static void test6829283() throws Exception { 238 BufferedReader reader; 239 java.net.Authenticator.setDefault(new KnowNothingAuthenticator()); 240 try { 241 new BufferedReader(new InputStreamReader( 242 webUrl.openConnection().getInputStream())); 243 } catch (IOException ioe) { 244 // Will fail since no username and password is provided. 245 } 246 if (count > 1) { 247 throw new RuntimeException("Authenticator called twice"); 248 } 249 } 250 251 /** 252 * Creates and starts an HTTP or proxy server that requires 253 * Negotiate authentication. 254 * @param scheme "Negotiate" or "Kerberos" 255 * @param principal the krb5 service principal the server runs with 256 * @return the server 257 */ 258 public static HttpServer httpd(String scheme, boolean proxy, 259 String principal, String ktab) throws Exception { 260 MyHttpHandler h = new MyHttpHandler(); 261 HttpServer server = HttpServer.create(new InetSocketAddress(0), 0); 262 HttpContext hc = server.createContext("/", h); 263 hc.setAuthenticator(new MyServerAuthenticator( 264 proxy, scheme, principal, ktab)); 265 server.start(); 266 return server; 267 } 268 269 static class MyHttpHandler implements HttpHandler { 270 public void handle(HttpExchange t) throws IOException { 271 t.sendResponseHeaders(200, 0); 272 t.getResponseBody().write(CONTENT.getBytes()); 273 t.close(); 274 } 275 } 276 277 static class MyServerAuthenticator 278 extends com.sun.net.httpserver.Authenticator { 279 Subject s = new Subject(); 280 GSSManager m = null; 281 GSSCredential cred = null; 282 String scheme = null; 283 String reqHdr = "WWW-Authenticate"; 284 String respHdr = "Authorization"; 285 int err = HttpURLConnection.HTTP_UNAUTHORIZED; 286 287 public MyServerAuthenticator(boolean proxy, String scheme, 288 String principal, String ktab) throws Exception { 289 290 this.scheme = scheme; 291 if (proxy) { 292 reqHdr = "Proxy-Authenticate"; 293 respHdr = "Proxy-Authorization"; 294 err = HttpURLConnection.HTTP_PROXY_AUTH; 295 } 296 297 Krb5LoginModule krb5 = new Krb5LoginModule(); 298 Map<String, String> map = new HashMap<>(); 299 Map<String, Object> shared = new HashMap<>(); 300 301 map.put("storeKey", "true"); 302 map.put("isInitiator", "false"); 303 map.put("useKeyTab", "true"); 304 map.put("keyTab", ktab); 305 map.put("principal", principal); 306 krb5.initialize(s, null, shared, map); 307 krb5.login(); 308 krb5.commit(); 309 m = GSSManager.getInstance(); 310 cred = Subject.doAs(s, new PrivilegedExceptionAction<GSSCredential>() { 311 @Override 312 public GSSCredential run() throws Exception { 313 System.err.println("Creating GSSCredential"); 314 return m.createCredential( 315 null, 316 GSSCredential.INDEFINITE_LIFETIME, 317 MyServerAuthenticator.this.scheme.equalsIgnoreCase("Negotiate")? 318 GSSUtil.GSS_SPNEGO_MECH_OID: 319 GSSUtil.GSS_KRB5_MECH_OID, 320 GSSCredential.ACCEPT_ONLY); 321 } 322 }); 323 } 324 325 @Override 326 public Result authenticate(HttpExchange exch) { 327 // The GSContext is stored in an HttpContext attribute named 328 // "GSSContext" and is created at the first request. 329 GSSContext c = null; 330 String auth = exch.getRequestHeaders().getFirst(respHdr); 331 try { 332 c = (GSSContext)exch.getHttpContext().getAttributes().get("GSSContext"); 333 if (auth == null) { // First request 334 Headers map = exch.getResponseHeaders(); 335 map.set (reqHdr, scheme); // Challenge! 336 c = Subject.doAs(s, new PrivilegedExceptionAction<GSSContext>() { 337 @Override 338 public GSSContext run() throws Exception { 339 return m.createContext(cred); 340 } 341 }); 342 exch.getHttpContext().getAttributes().put("GSSContext", c); 343 return new com.sun.net.httpserver.Authenticator.Retry(err); 344 } else { // Later requests 345 byte[] token = Base64.getMimeDecoder().decode(auth.split(" ")[1]); 346 token = c.acceptSecContext(token, 0, token.length); 347 Headers map = exch.getResponseHeaders(); 348 map.set (reqHdr, scheme + " " + Base64.getMimeEncoder() 349 .encodeToString(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 }