7110803: SASL service for multiple hostnames
authorweijun
Fri, 02 Nov 2012 10:48:04 +0800
changeset 14340 e150cbaf584e
parent 14339 3b561cef789b
child 14341 d06c0d29521c
7110803: SASL service for multiple hostnames Reviewed-by: mullan
jdk/src/share/classes/com/sun/security/ntlm/Server.java
jdk/src/share/classes/com/sun/security/sasl/CramMD5Server.java
jdk/src/share/classes/com/sun/security/sasl/digest/DigestMD5Base.java
jdk/src/share/classes/com/sun/security/sasl/digest/DigestMD5Server.java
jdk/src/share/classes/com/sun/security/sasl/gsskerb/GssKrb5Server.java
jdk/src/share/classes/com/sun/security/sasl/ntlm/NTLMServer.java
jdk/src/share/classes/com/sun/security/sasl/util/AbstractSaslImpl.java
jdk/src/share/classes/javax/security/sasl/Sasl.java
jdk/src/share/classes/javax/security/sasl/SaslServerFactory.java
jdk/test/com/sun/security/sasl/digest/Unbound.java
jdk/test/sun/security/krb5/auto/SaslBasic.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};
     }
 
     /**
--- 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<String, ?> props,
         CallbackHandler cbh) throws SaslException {
--- 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);
             }
--- 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<String> serverRealms;
 
     DigestMD5Server(String protocol, String serverName, Map<String, ?> 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<String>();
 
@@ -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
--- 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;
+    }
 }
--- 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:
--- 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,
--- 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.
+     * <br>The value of this constant is
+     * <tt>"javax.security.sasl.bound.server.name"</tt>.
+     */
+    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 <tt>SaslClient</tt>/<tt>SaslServer</tt>.
      * 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 <code>SaslException</code> 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.
--- 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
+     * <code>SaslException</code> 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 <tt>Sasl</tt> class for a list of standard properties.
--- /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();
+    }
+}
--- /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");
+        }
+    }
+}