提交 5ae8f683 编写于 作者: W weijun

7110803: SASL service for multiple hostnames

Reviewed-by: mullan
上级 19246f24
......@@ -106,7 +106,7 @@ public abstract class Server extends NTLM {
* 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 @@ public abstract class Server extends NTLM {
throw new NTLMException(NTLMException.AUTH_FAILED,
"None of LM and NTLM verified");
}
return new String[] {username, hostname};
return new String[] {username, hostname, incomingDomain};
}
/**
......
......@@ -58,14 +58,12 @@ final class CramMD5Server extends CramMD5Base implements SaslServer {
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 {
......
......@@ -249,6 +249,8 @@ abstract class DigestMD5Base extends AbstractSaslImpl {
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);
}
......
......@@ -141,8 +141,10 @@ final class DigestMD5Server extends DigestMD5Base implements SaslServer {
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 @@ final class DigestMD5Server extends DigestMD5Base implements SaslServer {
// 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 @@ final class DigestMD5Server extends DigestMD5Base implements SaslServer {
// 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 @@ final class DigestMD5Server extends DigestMD5Base implements SaslServer {
}
}
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
......
......@@ -67,9 +67,14 @@ final class GssKrb5Server extends GssKrb5Base implements SaslServer {
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 @@ final class GssKrb5Server extends GssKrb5Base implements SaslServer {
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 @@ final class GssKrb5Server extends GssKrb5Base implements SaslServer {
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 @@ final class GssKrb5Server extends GssKrb5Base implements SaslServer {
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 @@ final class GssKrb5Server extends GssKrb5Base implements SaslServer {
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;
}
}
......@@ -99,6 +99,7 @@ final class NTLMServer implements SaslServer {
private String authzId;
private final String mech;
private String hostname;
private String target;
/**
* @param mech not null
......@@ -180,6 +181,7 @@ final class NTLMServer implements SaslServer {
String[] out = server.verify(response, nonce);
authzId = out[0];
hostname = out[1];
target = out[2];
return null;
}
} catch (NTLMException ex) {
......@@ -220,6 +222,8 @@ final class NTLMServer implements SaslServer {
switch (propName) {
case Sasl.QOP:
return "auth";
case Sasl.BOUND_SERVER_NAME:
return target;
case NTLM_HOSTNAME:
return hostname;
default:
......
......@@ -149,7 +149,8 @@ public abstract class AbstractSaslImpl {
/**
* 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 @@ public abstract class AbstractSaslImpl {
*/
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,
......
......@@ -118,6 +118,18 @@ public class Sasl {
public static final String SERVER_AUTH =
"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>.
......@@ -449,7 +461,10 @@ public class Sasl {
* 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.
......
......@@ -60,8 +60,10 @@ public abstract interface SaslServerFactory {
* 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.
......
/*
* 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();
}
}
/*
* 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");
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册