417 lines
17 KiB
Java
417 lines
17 KiB
Java
|
/*
|
||
|
* Copyright (c) 2010, 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 6911951
|
||
|
* @summary NTLM should be a supported Java SASL mechanism
|
||
|
*/
|
||
|
import java.io.IOException;
|
||
|
import javax.security.sasl.*;
|
||
|
import javax.security.auth.callback.*;
|
||
|
import java.util.*;
|
||
|
|
||
|
import com.sun.security.ntlm.NTLMException;
|
||
|
|
||
|
public class NTLMTest {
|
||
|
|
||
|
private static final String MECH = "NTLM";
|
||
|
private static final String REALM = "REALM";
|
||
|
private static final String PROTOCOL = "jmx";
|
||
|
private static final byte[] EMPTY = new byte[0];
|
||
|
|
||
|
private static final String USER1 = "dummy";
|
||
|
private static final char[] PASS1 = "bogus".toCharArray();
|
||
|
private static final String USER2 = "foo";
|
||
|
private static final char[] PASS2 = "bar".toCharArray();
|
||
|
|
||
|
private static final Map<String,char[]> maps =
|
||
|
new HashMap<String,char[]>();
|
||
|
static {
|
||
|
maps.put(USER1, PASS1);
|
||
|
maps.put(USER2, PASS2);
|
||
|
}
|
||
|
|
||
|
static char[] getPass(String d, String u) {
|
||
|
if (!d.equals(REALM)) return null;
|
||
|
return maps.get(u);
|
||
|
}
|
||
|
|
||
|
public static void main(String[] args) throws Exception {
|
||
|
|
||
|
checkAuthOnly();
|
||
|
checkClientNameOverride();
|
||
|
checkServerDomainOverride();
|
||
|
checkClientDomainOverride();
|
||
|
checkVersions();
|
||
|
checkClientHostname();
|
||
|
}
|
||
|
|
||
|
static void checkVersions() throws Exception {
|
||
|
// Server accepts all version
|
||
|
checkVersion(null, null);
|
||
|
checkVersion("LM/NTLM", null);
|
||
|
checkVersion("LM", null);
|
||
|
checkVersion("NTLM", null);
|
||
|
checkVersion("NTLM2", null);
|
||
|
checkVersion("LMv2/NTLMv2", null);
|
||
|
checkVersion("LMv2", null);
|
||
|
checkVersion("NTLMv2", null);
|
||
|
|
||
|
// Client's default version is LMv2
|
||
|
checkVersion(null, "LMv2");
|
||
|
|
||
|
// Also works if they specified identical versions
|
||
|
checkVersion("LM/NTLM", "LM");
|
||
|
checkVersion("LM", "LM");
|
||
|
checkVersion("NTLM", "LM");
|
||
|
checkVersion("NTLM2", "NTLM2");
|
||
|
checkVersion("LMv2/NTLMv2", "LMv2");
|
||
|
checkVersion("LMv2", "LMv2");
|
||
|
checkVersion("NTLMv2", "LMv2");
|
||
|
|
||
|
// But should not work if different
|
||
|
try {
|
||
|
checkVersion("LM/NTLM", "LMv2");
|
||
|
throw new Exception("Should not succeed");
|
||
|
} catch (SaslException se) {
|
||
|
NTLMException ne = (NTLMException)se.getCause();
|
||
|
if (ne.errorCode() != NTLMException.AUTH_FAILED) {
|
||
|
throw new Exception("Failed false");
|
||
|
}
|
||
|
}
|
||
|
try {
|
||
|
checkVersion("LMv2/NTLMv2", "LM");
|
||
|
throw new Exception("Should not succeed");
|
||
|
} catch (SaslException se) {
|
||
|
NTLMException ne = (NTLMException)se.getCause();
|
||
|
if (ne.errorCode() != NTLMException.AUTH_FAILED) {
|
||
|
throw new Exception("Failed false");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A test on version matching
|
||
|
* @param vc ntlm version specified for client
|
||
|
* @param vs ntlm version specified for server
|
||
|
* @throws Exception
|
||
|
*/
|
||
|
private static void checkVersion(String vc, String vs) throws Exception {
|
||
|
Map<String,Object> pc = new HashMap<>();
|
||
|
pc.put("com.sun.security.sasl.ntlm.version", vc);
|
||
|
Map<String,Object> ps = new HashMap<>();
|
||
|
ps.put("com.sun.security.sasl.ntlm.version", vs);
|
||
|
SaslClient clnt = Sasl.createSaslClient(
|
||
|
new String[]{MECH}, USER1, PROTOCOL, null, pc,
|
||
|
new CallbackHandler() {
|
||
|
public void handle(Callback[] callbacks)
|
||
|
throws IOException, UnsupportedCallbackException {
|
||
|
for (Callback cb: callbacks) {
|
||
|
if (cb instanceof NameCallback) {
|
||
|
NameCallback ncb = (NameCallback)cb;
|
||
|
ncb.setName(ncb.getDefaultName());
|
||
|
} else if (cb instanceof PasswordCallback) {
|
||
|
((PasswordCallback)cb).setPassword(PASS1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
SaslServer srv = Sasl.createSaslServer(MECH, PROTOCOL, REALM, ps,
|
||
|
new CallbackHandler() {
|
||
|
public void handle(Callback[] callbacks)
|
||
|
throws IOException, UnsupportedCallbackException {
|
||
|
String domain = null, name = null;
|
||
|
PasswordCallback pcb = null;
|
||
|
for (Callback cb: callbacks) {
|
||
|
if (cb instanceof NameCallback) {
|
||
|
name = ((NameCallback)cb).getDefaultName();
|
||
|
} else if (cb instanceof RealmCallback) {
|
||
|
domain = ((RealmCallback)cb).getDefaultText();
|
||
|
} else if (cb instanceof PasswordCallback) {
|
||
|
pcb = (PasswordCallback)cb;
|
||
|
}
|
||
|
}
|
||
|
if (pcb != null) {
|
||
|
pcb.setPassword(getPass(domain, name));
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
handshake(clnt, srv);
|
||
|
}
|
||
|
|
||
|
private static void checkClientHostname() throws Exception {
|
||
|
Map<String,Object> pc = new HashMap<>();
|
||
|
pc.put("com.sun.security.sasl.ntlm.hostname", "this.is.com");
|
||
|
SaslClient clnt = Sasl.createSaslClient(
|
||
|
new String[]{MECH}, USER1, PROTOCOL, null, pc,
|
||
|
new CallbackHandler() {
|
||
|
public void handle(Callback[] callbacks)
|
||
|
throws IOException, UnsupportedCallbackException {
|
||
|
for (Callback cb: callbacks) {
|
||
|
if (cb instanceof NameCallback) {
|
||
|
NameCallback ncb = (NameCallback)cb;
|
||
|
ncb.setName(ncb.getDefaultName());
|
||
|
} else if (cb instanceof PasswordCallback) {
|
||
|
((PasswordCallback)cb).setPassword(PASS1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
SaslServer srv = Sasl.createSaslServer(MECH, PROTOCOL, REALM, null,
|
||
|
new CallbackHandler() {
|
||
|
public void handle(Callback[] callbacks)
|
||
|
throws IOException, UnsupportedCallbackException {
|
||
|
String domain = null, name = null;
|
||
|
PasswordCallback pcb = null;
|
||
|
for (Callback cb: callbacks) {
|
||
|
if (cb instanceof NameCallback) {
|
||
|
name = ((NameCallback)cb).getDefaultName();
|
||
|
} else if (cb instanceof RealmCallback) {
|
||
|
domain = ((RealmCallback)cb).getDefaultText();
|
||
|
} else if (cb instanceof PasswordCallback) {
|
||
|
pcb = (PasswordCallback)cb;
|
||
|
}
|
||
|
}
|
||
|
if (pcb != null) {
|
||
|
pcb.setPassword(getPass(domain, name));
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
handshake(clnt, srv);
|
||
|
if (!"this.is.com".equals(
|
||
|
srv.getNegotiatedProperty("com.sun.security.sasl.ntlm.hostname"))) {
|
||
|
throw new Exception("Hostname not trasmitted to server");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Client realm override, but finally overridden by server response
|
||
|
*/
|
||
|
private static void checkClientDomainOverride() throws Exception {
|
||
|
SaslClient clnt = Sasl.createSaslClient(
|
||
|
new String[]{MECH}, USER1, PROTOCOL, "ANOTHERREALM", null,
|
||
|
new CallbackHandler() {
|
||
|
public void handle(Callback[] callbacks)
|
||
|
throws IOException, UnsupportedCallbackException {
|
||
|
for (Callback cb: callbacks) {
|
||
|
if (cb instanceof NameCallback) {
|
||
|
NameCallback ncb = (NameCallback)cb;
|
||
|
ncb.setName(ncb.getDefaultName());
|
||
|
} else if(cb instanceof RealmCallback) {
|
||
|
RealmCallback dcb = (RealmCallback)cb;
|
||
|
dcb.setText("THIRDDOMAIN");
|
||
|
} else if (cb instanceof PasswordCallback) {
|
||
|
((PasswordCallback)cb).setPassword(PASS1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
SaslServer srv = Sasl.createSaslServer(MECH, PROTOCOL, REALM, null,
|
||
|
new CallbackHandler() {
|
||
|
public void handle(Callback[] callbacks)
|
||
|
throws IOException, UnsupportedCallbackException {
|
||
|
String domain = null, name = null;
|
||
|
PasswordCallback pcb = null;
|
||
|
for (Callback cb: callbacks) {
|
||
|
if (cb instanceof NameCallback) {
|
||
|
name = ((NameCallback)cb).getDefaultName();
|
||
|
} else if (cb instanceof RealmCallback) {
|
||
|
domain = ((RealmCallback)cb).getDefaultText();
|
||
|
} else if (cb instanceof PasswordCallback) {
|
||
|
pcb = (PasswordCallback)cb;
|
||
|
}
|
||
|
}
|
||
|
if (pcb != null) {
|
||
|
pcb.setPassword(getPass(domain, name));
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
handshake(clnt, srv);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Client side user name provided in callback.
|
||
|
* @throws Exception
|
||
|
*/
|
||
|
private static void checkClientNameOverride() throws Exception {
|
||
|
SaslClient clnt = Sasl.createSaslClient(
|
||
|
new String[]{MECH}, null, PROTOCOL, null, null,
|
||
|
new CallbackHandler() {
|
||
|
public void handle(Callback[] callbacks)
|
||
|
throws IOException, UnsupportedCallbackException {
|
||
|
for (Callback cb: callbacks) {
|
||
|
if (cb instanceof NameCallback) {
|
||
|
NameCallback ncb = (NameCallback)cb;
|
||
|
ncb.setName(USER1);
|
||
|
} else if (cb instanceof PasswordCallback) {
|
||
|
((PasswordCallback)cb).setPassword(PASS1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
SaslServer srv = Sasl.createSaslServer(MECH, PROTOCOL, REALM, null,
|
||
|
new CallbackHandler() {
|
||
|
public void handle(Callback[] callbacks)
|
||
|
throws IOException, UnsupportedCallbackException {
|
||
|
String domain = null, name = null;
|
||
|
PasswordCallback pcb = null;
|
||
|
for (Callback cb: callbacks) {
|
||
|
if (cb instanceof NameCallback) {
|
||
|
name = ((NameCallback)cb).getDefaultName();
|
||
|
} else if (cb instanceof RealmCallback) {
|
||
|
domain = ((RealmCallback)cb).getDefaultText();
|
||
|
} else if (cb instanceof PasswordCallback) {
|
||
|
pcb = (PasswordCallback)cb;
|
||
|
}
|
||
|
}
|
||
|
if (pcb != null) {
|
||
|
pcb.setPassword(getPass(domain, name));
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
handshake(clnt, srv);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* server side domain provided in props.
|
||
|
* @throws Exception
|
||
|
*/
|
||
|
private static void checkServerDomainOverride() throws Exception {
|
||
|
SaslClient clnt = Sasl.createSaslClient(
|
||
|
new String[]{MECH}, USER1, PROTOCOL, null, null,
|
||
|
new CallbackHandler() {
|
||
|
public void handle(Callback[] callbacks)
|
||
|
throws IOException, UnsupportedCallbackException {
|
||
|
for (Callback cb: callbacks) {
|
||
|
if (cb instanceof NameCallback) {
|
||
|
NameCallback ncb = (NameCallback)cb;
|
||
|
ncb.setName(ncb.getDefaultName());
|
||
|
} else if (cb instanceof PasswordCallback) {
|
||
|
((PasswordCallback)cb).setPassword(PASS1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
Map<String,Object> ps = new HashMap<>();
|
||
|
ps.put("com.sun.security.sasl.ntlm.domain", REALM);
|
||
|
SaslServer srv = Sasl.createSaslServer(MECH, PROTOCOL, null, ps,
|
||
|
new CallbackHandler() {
|
||
|
public void handle(Callback[] callbacks)
|
||
|
throws IOException, UnsupportedCallbackException {
|
||
|
String domain = null, name = null;
|
||
|
PasswordCallback pcb = null;
|
||
|
for (Callback cb: callbacks) {
|
||
|
if (cb instanceof NameCallback) {
|
||
|
name = ((NameCallback)cb).getDefaultName();
|
||
|
} else if (cb instanceof RealmCallback) {
|
||
|
domain = ((RealmCallback)cb).getDefaultText();
|
||
|
} else if (cb instanceof PasswordCallback) {
|
||
|
pcb = (PasswordCallback)cb;
|
||
|
}
|
||
|
}
|
||
|
if (pcb != null) {
|
||
|
pcb.setPassword(getPass(domain, name));
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
handshake(clnt, srv);
|
||
|
}
|
||
|
|
||
|
private static void checkAuthOnly() throws Exception {
|
||
|
Map<String,Object> props = new HashMap<>();
|
||
|
props.put(Sasl.QOP, "auth-conf");
|
||
|
try {
|
||
|
Sasl.createSaslClient(
|
||
|
new String[]{MECH}, USER2, PROTOCOL, REALM, props, null);
|
||
|
throw new Exception("NTLM should not support auth-conf");
|
||
|
} catch (SaslException se) {
|
||
|
// Normal
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void handshake(SaslClient clnt, SaslServer srv)
|
||
|
throws Exception {
|
||
|
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);
|
||
|
System.out.println("Initial:");
|
||
|
new sun.misc.HexDumpEncoder().encodeBuffer(response, System.out);
|
||
|
byte[] challenge;
|
||
|
|
||
|
while (!clnt.isComplete() || !srv.isComplete()) {
|
||
|
challenge = srv.evaluateResponse(response);
|
||
|
response = null;
|
||
|
if (challenge != null) {
|
||
|
System.out.println("Challenge:");
|
||
|
new sun.misc.HexDumpEncoder().encodeBuffer(challenge, System.out);
|
||
|
response = clnt.evaluateChallenge(challenge);
|
||
|
}
|
||
|
if (response != null) {
|
||
|
System.out.println("Response:");
|
||
|
new sun.misc.HexDumpEncoder().encodeBuffer(response, System.out);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (clnt.isComplete() && srv.isComplete()) {
|
||
|
System.out.println("SUCCESS");
|
||
|
if (!srv.getAuthorizationID().equals(USER1)) {
|
||
|
throw new Exception("Not correct user");
|
||
|
}
|
||
|
} else {
|
||
|
throw new IllegalStateException(
|
||
|
"FAILURE: mismatched state:"
|
||
|
+ " client complete? " + clnt.isComplete()
|
||
|
+ " server complete? " + srv.isComplete());
|
||
|
}
|
||
|
|
||
|
if (!clnt.getNegotiatedProperty(Sasl.QOP).equals("auth") ||
|
||
|
!srv.getNegotiatedProperty(Sasl.QOP).equals("auth") ||
|
||
|
!clnt.getNegotiatedProperty(
|
||
|
"com.sun.security.sasl.ntlm.domain").equals(REALM)) {
|
||
|
throw new Exception("Negotiated property error");
|
||
|
}
|
||
|
clnt.dispose();
|
||
|
srv.dispose();
|
||
|
}
|
||
|
}
|