# HG changeset patch # User weijun # Date 1351824484 -28800 # Node ID e150cbaf584e67ac69649709ecbd1e1718848666 # Parent 3b561cef789bacf754dff8fe1af943cacab2abd0 7110803: SASL service for multiple hostnames Reviewed-by: mullan diff -r 3b561cef789b -r e150cbaf584e jdk/src/share/classes/com/sun/security/ntlm/Server.java --- a/jdk/src/share/classes/com/sun/security/ntlm/Server.java Thu Nov 01 18:09:43 2012 -0400 +++ b/jdk/src/share/classes/com/sun/security/ntlm/Server.java Fri Nov 02 10:48:04 2012 +0800 @@ -106,7 +106,7 @@ * various negotiated information. * @param type3 the incoming Type3 message from client, must not be null * @param nonce the same nonce provided in {@link #type2}, must not be null - * @return username and hostname of the client in a byte array + * @return client username, client hostname, and the request target * @throws NTLMException if the incoming message is invalid, or * {@code nonce} is null. */ @@ -194,7 +194,7 @@ throw new NTLMException(NTLMException.AUTH_FAILED, "None of LM and NTLM verified"); } - return new String[] {username, hostname}; + return new String[] {username, hostname, incomingDomain}; } /** diff -r 3b561cef789b -r e150cbaf584e jdk/src/share/classes/com/sun/security/sasl/CramMD5Server.java --- a/jdk/src/share/classes/com/sun/security/sasl/CramMD5Server.java Thu Nov 01 18:09:43 2012 -0400 +++ b/jdk/src/share/classes/com/sun/security/sasl/CramMD5Server.java Fri Nov 02 10:48:04 2012 +0800 @@ -58,14 +58,12 @@ private CallbackHandler cbh; /** - * Creates a SASL mechanism with client credentials that it needs - * to participate in CRAM-MD5 authentication exchange with the server. + * Creates a CRAM-MD5 SASL server. * - * @param authID A non-null string representing the principal - * being authenticated. - * - * @param pw A non-null String or byte[] - * containing the password. If it is an array, it is first cloned. + * @param protocol ignored in CRAM-MD5 + * @param serverFqdn non-null, used in generating a challenge + * @param props ignored in CRAM-MD5 + * @param cbh find password, authorize user */ CramMD5Server(String protocol, String serverFqdn, Map props, CallbackHandler cbh) throws SaslException { diff -r 3b561cef789b -r e150cbaf584e jdk/src/share/classes/com/sun/security/sasl/digest/DigestMD5Base.java --- a/jdk/src/share/classes/com/sun/security/sasl/digest/DigestMD5Base.java Thu Nov 01 18:09:43 2012 -0400 +++ b/jdk/src/share/classes/com/sun/security/sasl/digest/DigestMD5Base.java Fri Nov 02 10:48:04 2012 +0800 @@ -249,6 +249,8 @@ if (completed) { if (propName.equals(Sasl.STRENGTH)) { return negotiatedStrength; + } else if (propName.equals(Sasl.BOUND_SERVER_NAME)) { + return digestUri.substring(digestUri.indexOf('/') + 1); } else { return super.getNegotiatedProperty(propName); } diff -r 3b561cef789b -r e150cbaf584e jdk/src/share/classes/com/sun/security/sasl/digest/DigestMD5Server.java --- a/jdk/src/share/classes/com/sun/security/sasl/digest/DigestMD5Server.java Thu Nov 01 18:09:43 2012 -0400 +++ b/jdk/src/share/classes/com/sun/security/sasl/digest/DigestMD5Server.java Fri Nov 02 10:48:04 2012 +0800 @@ -141,8 +141,10 @@ private List serverRealms; DigestMD5Server(String protocol, String serverName, Map props, - CallbackHandler cbh) throws SaslException { - super(props, MY_CLASS_NAME, 1, protocol + "/" + serverName, cbh); + CallbackHandler cbh) throws SaslException { + super(props, MY_CLASS_NAME, 1, + protocol + "/" + (serverName==null?"*":serverName), + cbh); serverRealms = new ArrayList(); @@ -173,7 +175,12 @@ // By default, use server name as realm if (serverRealms.isEmpty()) { - serverRealms.add(serverName); + if (serverName == null) { + throw new SaslException( + "A realm must be provided in props or serverName"); + } else { + serverRealms.add(serverName); + } } } @@ -539,7 +546,7 @@ // host should match one of service's configured service names // Check against digest URI that mech was created with - if (digestUri.equalsIgnoreCase(digestUriFromResponse)) { + if (uriMatches(digestUri, digestUriFromResponse)) { digestUri = digestUriFromResponse; // account for case-sensitive diffs } else { throw new SaslException("DIGEST-MD5: digest response format " + @@ -653,6 +660,21 @@ } } + private static boolean uriMatches(String thisUri, String incomingUri) { + // Full match + if (thisUri.equalsIgnoreCase(incomingUri)) { + return true; + } + // Unbound match + if (thisUri.endsWith("/*")) { + int protoAndSlash = thisUri.length() - 1; + String thisProtoAndSlash = thisUri.substring(0, protoAndSlash); + String incomingProtoAndSlash = incomingUri.substring(0, protoAndSlash); + return thisProtoAndSlash.equalsIgnoreCase(incomingProtoAndSlash); + } + return false; + } + /** * Server sends a message formatted as follows: * response-auth = "rspauth" "=" response-value diff -r 3b561cef789b -r e150cbaf584e jdk/src/share/classes/com/sun/security/sasl/gsskerb/GssKrb5Server.java --- a/jdk/src/share/classes/com/sun/security/sasl/gsskerb/GssKrb5Server.java Thu Nov 01 18:09:43 2012 -0400 +++ b/jdk/src/share/classes/com/sun/security/sasl/gsskerb/GssKrb5Server.java Fri Nov 02 10:48:04 2012 +0800 @@ -67,9 +67,14 @@ private int handshakeStage = 0; private String peer; + private String me; private String authzid; private CallbackHandler cbh; + // When serverName is null, the server will be unbound. We need to save and + // check the protocol name after the context is established. This value + // will be null if serverName is not null. + private final String protocolSaved; /** * Creates a SASL mechanism with server credentials that it needs * to participate in GSS-API/Kerberos v5 authentication exchange @@ -81,7 +86,15 @@ super(props, MY_CLASS_NAME); this.cbh = cbh; - String service = protocol + "@" + serverName; + + String service; + if (serverName == null) { + protocolSaved = protocol; + service = null; + } else { + protocolSaved = null; + service = protocol + "@" + serverName; + } logger.log(Level.FINE, "KRB5SRV01:Using service name: {0}", service); @@ -89,8 +102,8 @@ GSSManager mgr = GSSManager.getInstance(); // Create the name for the requested service entity for Krb5 mech - GSSName serviceName = mgr.createName(service, - GSSName.NT_HOSTBASED_SERVICE, KRB5_OID); + GSSName serviceName = service == null ? null: + mgr.createName(service, GSSName.NT_HOSTBASED_SERVICE, KRB5_OID); GSSCredential cred = mgr.createCredential(serviceName, GSSCredential.INDEFINITE_LIFETIME, @@ -163,8 +176,18 @@ handshakeStage = 1; peer = secCtx.getSrcName().toString(); + me = secCtx.getTargName().toString(); - logger.log(Level.FINE, "KRB5SRV05:Peer name is : {0}", peer); + logger.log(Level.FINE, + "KRB5SRV05:Peer name is : {0}, my name is : {1}", + new Object[]{peer, me}); + + // me might take the form of proto@host or proto/host + if (protocolSaved != null && + !protocolSaved.equalsIgnoreCase(me.split("[/@]")[0])) { + throw new SaslException( + "GSS context targ name protocol error: " + me); + } if (gssOutToken == null) { return doHandshake1(EMPTY); @@ -319,4 +342,25 @@ throw new IllegalStateException("Authentication incomplete"); } } + + public Object getNegotiatedProperty(String propName) { + if (!completed) { + throw new IllegalStateException("Authentication incomplete"); + } + + Object result; + switch (propName) { + case Sasl.BOUND_SERVER_NAME: + try { + // me might take the form of proto@host or proto/host + result = me.split("[/@]")[1]; + } catch (Exception e) { + result = null; + } + break; + default: + result = super.getNegotiatedProperty(propName); + } + return result; + } } diff -r 3b561cef789b -r e150cbaf584e jdk/src/share/classes/com/sun/security/sasl/ntlm/NTLMServer.java --- a/jdk/src/share/classes/com/sun/security/sasl/ntlm/NTLMServer.java Thu Nov 01 18:09:43 2012 -0400 +++ b/jdk/src/share/classes/com/sun/security/sasl/ntlm/NTLMServer.java Fri Nov 02 10:48:04 2012 +0800 @@ -99,6 +99,7 @@ private String authzId; private final String mech; private String hostname; + private String target; /** * @param mech not null @@ -180,6 +181,7 @@ String[] out = server.verify(response, nonce); authzId = out[0]; hostname = out[1]; + target = out[2]; return null; } } catch (NTLMException ex) { @@ -220,6 +222,8 @@ switch (propName) { case Sasl.QOP: return "auth"; + case Sasl.BOUND_SERVER_NAME: + return target; case NTLM_HOSTNAME: return hostname; default: diff -r 3b561cef789b -r e150cbaf584e jdk/src/share/classes/com/sun/security/sasl/util/AbstractSaslImpl.java --- a/jdk/src/share/classes/com/sun/security/sasl/util/AbstractSaslImpl.java Thu Nov 01 18:09:43 2012 -0400 +++ b/jdk/src/share/classes/com/sun/security/sasl/util/AbstractSaslImpl.java Fri Nov 02 10:48:04 2012 +0800 @@ -149,7 +149,8 @@ /** * Retrieves the negotiated property. - * @exception SaslException if this authentication exchange has not completed + * @exception IllegalStateException if this authentication exchange has + * not completed */ public Object getNegotiatedProperty(String propName) { if (!completed) { @@ -255,7 +256,9 @@ */ protected static final void traceOutput(String srcClass, String srcMethod, String traceTag, byte[] output) { - traceOutput(srcClass, srcMethod, traceTag, output, 0, output.length); + if (output != null) { + traceOutput(srcClass, srcMethod, traceTag, output, 0, output.length); + } } protected static final void traceOutput(String srcClass, String srcMethod, diff -r 3b561cef789b -r e150cbaf584e jdk/src/share/classes/javax/security/sasl/Sasl.java --- a/jdk/src/share/classes/javax/security/sasl/Sasl.java Thu Nov 01 18:09:43 2012 -0400 +++ b/jdk/src/share/classes/javax/security/sasl/Sasl.java Fri Nov 02 10:48:04 2012 +0800 @@ -119,6 +119,18 @@ "javax.security.sasl.server.authentication"; /** + * The name of a property that specifies the bound server name for + * an unbound server. A server is created as an unbound server by setting + * the {@code serverName} argument in {@link #createSaslServer} as null. + * The property contains the bound host name after the authentication + * exchange has completed. It is only available on the server side. + *
The value of this constant is + * "javax.security.sasl.bound.server.name". + */ + public static final String BOUND_SERVER_NAME = + "javax.security.sasl.bound.server.name"; + + /** * The name of a property that specifies the maximum size of the receive * buffer in bytes of SaslClient/SaslServer. * The property contains the string representation of an integer. @@ -449,7 +461,10 @@ * IANA-registered name of a SASL mechanism. (e.g. "GSSAPI", "CRAM-MD5"). * @param protocol The non-null string name of the protocol for which * the authentication is being performed (e.g., "ldap"). - * @param serverName The non-null fully qualified host name of the server. + * @param serverName The fully qualified host name of the server, or null + * if the server is not bound to any specific host name. If the mechanism + * does not allow an unbound server, a SaslException will + * be thrown. * @param props The possibly null set of properties used to * select the SASL mechanism and to configure the authentication * exchange of the selected mechanism. diff -r 3b561cef789b -r e150cbaf584e jdk/src/share/classes/javax/security/sasl/SaslServerFactory.java --- a/jdk/src/share/classes/javax/security/sasl/SaslServerFactory.java Thu Nov 01 18:09:43 2012 -0400 +++ b/jdk/src/share/classes/javax/security/sasl/SaslServerFactory.java Fri Nov 02 10:48:04 2012 +0800 @@ -60,8 +60,10 @@ * IANA-registered name of a SASL mechanism. (e.g. "GSSAPI", "CRAM-MD5"). * @param protocol The non-null string name of the protocol for which * the authentication is being performed (e.g., "ldap"). - * @param serverName The non-null fully qualified host name of the server - * to authenticate to. + * @param serverName The fully qualified host name of the server to + * authenticate to, or null if the server is not bound to any specific host + * name. If the mechanism does not allow an unbound server, a + * SaslException will be thrown. * @param props The possibly null set of properties used to select the SASL * mechanism and to configure the authentication exchange of the selected * mechanism. See the Sasl class for a list of standard properties. diff -r 3b561cef789b -r e150cbaf584e jdk/test/com/sun/security/sasl/digest/Unbound.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/com/sun/security/sasl/digest/Unbound.java Fri Nov 02 10:48:04 2012 +0800 @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2012, 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 7110803 + * @summary SASL service for multiple hostnames + * @run main Unbound jmx + * @run main/fail Unbound j + */ +import javax.security.sasl.*; +import javax.security.auth.callback.*; +import java.util.*; + +public class Unbound { + private static final String MECH = "DIGEST-MD5"; + private static final String SERVER_FQDN = "machineX.imc.org"; + private static final String PROTOCOL = "jmx"; + + private static final byte[] EMPTY = new byte[0]; + + private static String pwfile, namesfile, proxyfile; + private static boolean auto; + private static boolean verbose = false; + + private static void init(String[] args) throws Exception { + if (args.length == 1) { + pwfile = "pw.properties"; + namesfile = "names.properties"; + auto = true; + } else { + int i = 1; + if (args[i].equals("-m")) { + i++; + auto = false; + } + if (args.length > i) { + pwfile = args[i++]; + + if (args.length > i) { + namesfile = args[i++]; + + if (args.length > i) { + proxyfile = args[i]; + } + } + } else { + pwfile = "pw.properties"; + namesfile = "names.properties"; + } + } + } + + public static void main(String[] args) throws Exception { + + init(args); + + CallbackHandler clntCbh = new ClientCallbackHandler(auto); + + CallbackHandler srvCbh = + new PropertiesFileCallbackHandler(pwfile, namesfile, proxyfile); + + SaslClient clnt = Sasl.createSaslClient( + new String[]{MECH}, null, PROTOCOL, SERVER_FQDN, null, clntCbh); + + Map props = System.getProperties(); + props.put("com.sun.security.sasl.digest.realm", SERVER_FQDN); + + SaslServer srv = Sasl.createSaslServer(MECH, args[0], null, props, + srvCbh); + + if (clnt == null) { + throw new IllegalStateException( + "Unable to find client impl for " + MECH); + } + if (srv == null) { + throw new IllegalStateException( + "Unable to find server impl for " + MECH); + } + + byte[] response = (clnt.hasInitialResponse()? + clnt.evaluateChallenge(EMPTY) : EMPTY); + byte[] challenge; + + while (!clnt.isComplete() || !srv.isComplete()) { + challenge = srv.evaluateResponse(response); + + if (challenge != null) { + response = clnt.evaluateChallenge(challenge); + } + } + + if (clnt.isComplete() && srv.isComplete()) { + if (verbose) { + System.out.println("SUCCESS"); + System.out.println("authzid is " + srv.getAuthorizationID()); + } + } else { + throw new IllegalStateException( + "FAILURE: mismatched state:" + + " client complete? " + clnt.isComplete() + + " server complete? " + srv.isComplete()); + } + + if (!SERVER_FQDN.equalsIgnoreCase((String) + srv.getNegotiatedProperty(Sasl.BOUND_SERVER_NAME))) { + throw new Exception("Server side gets wrong requested server name"); + } + clnt.dispose(); + srv.dispose(); + } +} diff -r 3b561cef789b -r e150cbaf584e jdk/test/sun/security/krb5/auto/SaslBasic.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/sun/security/krb5/auto/SaslBasic.java Fri Nov 02 10:48:04 2012 +0800 @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2012, 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 7110803 + * @summary SASL service for multiple hostnames + * @compile -XDignore.symbol.file SaslBasic.java + * @run main/othervm SaslBasic bound + * @run main/othervm SaslBasic unbound + */ +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Locale; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.sasl.*; + +// The basic krb5 test skeleton you can copy from +public class SaslBasic { + + public static void main(String[] args) throws Exception { + + boolean bound = args[0].equals("bound"); + String name = "host." + OneKDC.REALM.toLowerCase(Locale.US); + + new OneKDC(null).writeJAASConf(); + System.setProperty("javax.security.auth.useSubjectCredsOnly", "false"); + + HashMap clntprops = new HashMap(); + clntprops.put(Sasl.QOP, "auth-conf"); + SaslClient sc = Sasl.createSaslClient( + new String[]{"GSSAPI"}, null, "server", + name, clntprops, null); + + final HashMap srvprops = new HashMap(); + srvprops.put(Sasl.QOP, "auth,auth-int,auth-conf"); + SaslServer ss = Sasl.createSaslServer("GSSAPI", "server", + bound? name: null, srvprops, + new CallbackHandler() { + public void handle(Callback[] callbacks) + throws IOException, UnsupportedCallbackException { + for (Callback cb : callbacks) { + if (cb instanceof RealmCallback) { + ((RealmCallback) cb).setText(OneKDC.REALM); + } else if (cb instanceof AuthorizeCallback) { + ((AuthorizeCallback) cb).setAuthorized(true); + } + } + } + }); + + byte[] token = new byte[0]; + while (!sc.isComplete() || !ss.isComplete()) { + if (!sc.isComplete()) { + token = sc.evaluateChallenge(token); + } + if (!ss.isComplete()) { + token = ss.evaluateResponse(token); + } + } + if (!bound) { + String boundName = (String)ss.getNegotiatedProperty(Sasl.BOUND_SERVER_NAME); + if (!boundName.equals(name)) { + throw new Exception("Wrong bound server name"); + } + } + byte[] hello = "hello".getBytes(); + token = sc.wrap(hello, 0, hello.length); + token = ss.unwrap(token, 0, token.length); + if (!Arrays.equals(hello, token)) { + throw new Exception("Message altered"); + } + } +}