提交 a748f118 编写于 作者: W weijun

6911951: NTLM should be a supported Java SASL mechanism

Reviewed-by: vinnie, michaelm
上级 6c93b5c9
/*
* 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/
package com.sun.security.ntlm;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Date;
import java.util.Locale;
/**
* The NTLM client. Not multi-thread enabled.<p>
* Example:
* <pre>
* Client client = new Client(null, "host", "dummy",
* "REALM", "t0pSeCr3t".toCharArray());
* byte[] type1 = client.type1();
* // Send type1 to server and receive response as type2
* byte[] type3 = client.type3(type2, nonce);
* // Send type3 to server
* </pre>
*/
public final class Client extends NTLM {
final private String hostname;
final private String username;
private String domain; // might be updated by Type 2 msg
private byte[] pw1, pw2;
/**
* Creates an NTLM Client instance.
* @param version the NTLM version to use, which can be:
* <ul>
* <li>LM/NTLM: Original NTLM v1
* <li>LM: Original NTLM v1, LM only
* <li>NTLM: Original NTLM v1, NTLM only
* <li>NTLM2: NTLM v1 with Client Challenge
* <li>LMv2/NTLMv2: NTLM v2
* <li>LMv2: NTLM v2, LM only
* <li>NTLMv2: NTLM v2, NTLM only
* </ul>
* If null, "LMv2/NTLMv2" will be used.
* @param hostname hostname of the client, can be null
* @param username username to be authenticated, must not be null
* @param domain domain of {@code username}, can be null
* @param password password for {@code username}, must not be not null.
* This method does not make any modification to this parameter, it neither
* needs to access the content of this parameter after this method call,
* so you are free to modify or nullify this parameter after this call.
* @throws NullPointerException if {@code username} or {@code password} is null.
* @throws NTLMException if {@code version} is illegal
*/
public Client(String version, String hostname, String username,
String domain, char[] password) throws NTLMException {
super(version);
if ((username == null || password == null)) {
throw new NullPointerException("username/password cannot be null");
}
this.hostname = hostname;
this.username = username;
this.domain = domain;
this.pw1 = getP1(password);
this.pw2 = getP2(password);
debug("NTLM Client: (h,u,t,version(v)) = (%s,%s,%s,%s(%s))\n",
hostname, username, domain, version, v.toString());
}
/**
* Generates the Type 1 message
* @return the message generated
*/
public byte[] type1() {
Writer p = new Writer(1, 32);
int flags = 0x8203;
if (hostname != null) {
flags |= 0x2000;
}
if (domain != null) {
flags |= 0x1000;
}
if (v != Version.NTLM) {
flags |= 0x80000;
}
p.writeInt(12, flags);
p.writeSecurityBuffer(24, hostname, false);
p.writeSecurityBuffer(16, domain, false);
debug("NTLM Client: Type 1 created\n");
debug(p.getBytes());
return p.getBytes();
}
/**
* Generates the Type 3 message
* @param type2 the responding Type 2 message from server, must not be null
* @param nonce random 8-byte array to be used in message generation,
* must not be null except for original NTLM v1
* @return the message generated
* @throws NullPointerException if {@code type2} or {@code nonce} is null
* for NTLM v1.
* @throws NTLMException if the incoming message is invalid
*/
public byte[] type3(byte[] type2, byte[] nonce) throws NTLMException {
if (type2 == null || (v != Version.NTLM && nonce == null)) {
throw new NullPointerException("type2 and nonce cannot be null");
}
debug("NTLM Client: Type 2 received\n");
debug(type2);
Reader r = new Reader(type2);
byte[] challenge = r.readBytes(24, 8);
int inputFlags = r.readInt(20);
boolean unicode = (inputFlags & 1) == 1;
String domainFromServer = r.readSecurityBuffer(12, unicode);
if (domainFromServer != null) {
domain = domainFromServer;
}
if (domain == null) {
throw new NTLMException(NTLMException.NO_DOMAIN_INFO,
"No domain info");
}
int flags = 0x88200 | (inputFlags & 3);
Writer p = new Writer(3, 64);
byte[] lm = null, ntlm = null;
p.writeSecurityBuffer(28, domain, unicode);
p.writeSecurityBuffer(36, username, unicode);
p.writeSecurityBuffer(44, hostname, unicode);
if (v == Version.NTLM) {
byte[] lmhash = calcLMHash(pw1);
byte[] nthash = calcNTHash(pw2);
if (writeLM) lm = calcResponse (lmhash, challenge);
if (writeNTLM) ntlm = calcResponse (nthash, challenge);
} else if (v == Version.NTLM2) {
byte[] nthash = calcNTHash(pw2);
lm = ntlm2LM(nonce);
ntlm = ntlm2NTLM(nthash, nonce, challenge);
} else {
byte[] nthash = calcNTHash(pw2);
if (writeLM) lm = calcV2(nthash,
username.toUpperCase(Locale.US)+domain, nonce, challenge);
if (writeNTLM) {
byte[] alist = type2.length > 48 ?
r.readSecurityBuffer(40) : new byte[0];
byte[] blob = new byte[32+alist.length];
System.arraycopy(new byte[]{1,1,0,0,0,0,0,0}, 0, blob, 0, 8);
// TS
byte[] time = BigInteger.valueOf(new Date().getTime())
.add(new BigInteger("11644473600000"))
.multiply(BigInteger.valueOf(10000))
.toByteArray();
for (int i=0; i<time.length; i++) {
blob[8+time.length-i-1] = time[i];
}
System.arraycopy(nonce, 0, blob, 16, 8);
System.arraycopy(new byte[]{0,0,0,0}, 0, blob, 24, 4);
System.arraycopy(alist, 0, blob, 28, alist.length);
System.arraycopy(new byte[]{0,0,0,0}, 0,
blob, 28+alist.length, 4);
ntlm = calcV2(nthash, username.toUpperCase(Locale.US)+domain,
blob, challenge);
}
}
p.writeSecurityBuffer(12, lm);
p.writeSecurityBuffer(20, ntlm);
p.writeSecurityBuffer(52, new byte[0]);
p.writeInt(60, flags);
debug("NTLM Client: Type 3 created\n");
debug(p.getBytes());
return p.getBytes();
}
/**
* Returns the domain value provided by server after the authentication
* is complete, or the domain value provided by the client before it.
* @return the domain
*/
public String getDomain() {
return domain;
}
/**
* Disposes any password-derived information.
*/
public void dispose() {
Arrays.fill(pw1, (byte)0);
Arrays.fill(pw2, (byte)0);
}
}
/*
* 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/
package com.sun.security.ntlm;
import static com.sun.security.ntlm.Version.*;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.SecretKeySpec;
/**
* NTLM authentication implemented according to MS-NLMP, version 12.1
* @since 1.7
*/
class NTLM {
private final SecretKeyFactory fac;
private final Cipher cipher;
private final MessageDigest md4;
private final Mac hmac;
private final MessageDigest md5;
private static final boolean DEBUG =
System.getProperty("ntlm.debug") != null;
final Version v;
final boolean writeLM;
final boolean writeNTLM;
protected NTLM(String version) throws NTLMException {
if (version == null) version = "LMv2/NTLMv2";
switch (version) {
case "LM": v = NTLM; writeLM = true; writeNTLM = false; break;
case "NTLM": v = NTLM; writeLM = false; writeNTLM = true; break;
case "LM/NTLM": v = NTLM; writeLM = writeNTLM = true; break;
case "NTLM2": v = NTLM2; writeLM = writeNTLM = true; break;
case "LMv2": v = NTLMv2; writeLM = true; writeNTLM = false; break;
case "NTLMv2": v = NTLMv2; writeLM = false; writeNTLM = true; break;
case "LMv2/NTLMv2": v = NTLMv2; writeLM = writeNTLM = true; break;
default: throw new NTLMException(NTLMException.BAD_VERSION,
"Unknown version " + version);
}
try {
fac = SecretKeyFactory.getInstance ("DES");
cipher = Cipher.getInstance ("DES/ECB/NoPadding");
md4 = sun.security.provider.MD4.getInstance();
hmac = Mac.getInstance("HmacMD5");
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchPaddingException e) {
throw new AssertionError();
} catch (NoSuchAlgorithmException e) {
throw new AssertionError();
}
}
/**
* Prints out a formatted string, called in various places inside then NTLM
* implementation for debugging/logging purposes. When the system property
* "ntlm.debug" is set, <code>System.out.printf(format, args)</code> is
* called. This method is designed to be overridden by child classes to
* match their own debugging/logging mechanisms.
* @param format a format string
* @param args the arguments referenced by <code>format</code>
* @see java.io.PrintStream#printf(java.lang.String, java.lang.Object[])
*/
public void debug(String format, Object... args) {
if (DEBUG) {
System.out.printf(format, args);
}
}
/**
* Prints out the content of a byte array, called in various places inside
* the NTLM implementation for debugging/logging purposes. When the system
* property "ntlm.debug" is set, the hexdump of the array is printed into
* System.out. This method is designed to be overridden by child classes to
* match their own debugging/logging mechanisms.
* @param bytes the byte array to print out
*/
public void debug(byte[] bytes) {
if (DEBUG) {
try {
new sun.misc.HexDumpEncoder().encodeBuffer(bytes, System.out);
} catch (IOException ioe) {
// Impossible
}
}
}
/**
* Reading an NTLM packet
*/
static class Reader {
private final byte[] internal;
Reader(byte[] data) {
internal = data;
}
int readInt(int offset) throws NTLMException {
try {
return internal[offset] & 0xff +
(internal[offset+1] & 0xff << 8) +
(internal[offset+2] & 0xff << 16) +
(internal[offset+3] & 0xff << 24);
} catch (ArrayIndexOutOfBoundsException ex) {
throw new NTLMException(NTLMException.PACKET_READ_ERROR,
"Input message incorrect size");
}
}
int readShort(int offset) throws NTLMException {
try {
return internal[offset] & 0xff +
(internal[offset+1] & 0xff << 8);
} catch (ArrayIndexOutOfBoundsException ex) {
throw new NTLMException(NTLMException.PACKET_READ_ERROR,
"Input message incorrect size");
}
}
byte[] readBytes(int offset, int len) throws NTLMException {
try {
return Arrays.copyOfRange(internal, offset, offset + len);
} catch (ArrayIndexOutOfBoundsException ex) {
throw new NTLMException(NTLMException.PACKET_READ_ERROR,
"Input message incorrect size");
}
}
byte[] readSecurityBuffer(int offset) throws NTLMException {
int pos = readInt(offset+4);
if (pos == 0) return null;
try {
return Arrays.copyOfRange(
internal, pos, pos + readShort(offset));
} catch (ArrayIndexOutOfBoundsException ex) {
throw new NTLMException(NTLMException.PACKET_READ_ERROR,
"Input message incorrect size");
}
}
String readSecurityBuffer(int offset, boolean unicode)
throws NTLMException {
byte[] raw = readSecurityBuffer(offset);
try {
return raw == null ? null : new String(
raw, unicode ? "UnicodeLittleUnmarked" : "ISO8859_1");
} catch (UnsupportedEncodingException ex) {
throw new NTLMException(NTLMException.PACKET_READ_ERROR,
"Invalid input encoding");
}
}
}
/**
* Writing an NTLM packet
*/
static class Writer {
private byte[] internal; // buffer
private int current; // current written content interface buffer
/**
* Starts writing a NTLM packet
* @param type NEGOTIATE || CHALLENGE || AUTHENTICATE
* @param len the base length, without security buffers
*/
Writer(int type, int len) {
assert len < 256;
internal = new byte[256];
current = len;
System.arraycopy (
new byte[] {'N','T','L','M','S','S','P',0,(byte)type},
0, internal, 0, 9);
}
void writeShort(int offset, int number) {
internal[offset] = (byte)(number);
internal[offset+1] = (byte)(number >> 8);
}
void writeInt(int offset, int number) {
internal[offset] = (byte)(number);
internal[offset+1] = (byte)(number >> 8);
internal[offset+2] = (byte)(number >> 16);
internal[offset+3] = (byte)(number >> 24);
}
void writeBytes(int offset, byte[] data) {
System.arraycopy(data, 0, internal, offset, data.length);
}
void writeSecurityBuffer(int offset, byte[] data) {
if (data == null) {
writeShort(offset+4, current);
} else {
int len = data.length;
if (current + len > internal.length) {
internal = Arrays.copyOf(internal, current + len + 256);
}
writeShort(offset, len);
writeShort(offset+2, len);
writeShort(offset+4, current);
System.arraycopy(data, 0, internal, current, len);
current += len;
}
}
void writeSecurityBuffer(int offset, String str, boolean unicode) {
try {
writeSecurityBuffer(offset, str == null ? null : str.getBytes(
unicode ? "UnicodeLittleUnmarked" : "ISO8859_1"));
} catch (UnsupportedEncodingException ex) {
assert false;
}
}
byte[] getBytes() {
return Arrays.copyOf(internal, current);
}
}
// LM/NTLM
/* Convert a 7 byte array to an 8 byte array (for a des key with parity)
* input starts at offset off
*/
byte[] makeDesKey (byte[] input, int off) {
int[] in = new int [input.length];
for (int i=0; i<in.length; i++ ) {
in[i] = input[i]<0 ? input[i]+256: input[i];
}
byte[] out = new byte[8];
out[0] = (byte)in[off+0];
out[1] = (byte)(((in[off+0] << 7) & 0xFF) | (in[off+1] >> 1));
out[2] = (byte)(((in[off+1] << 6) & 0xFF) | (in[off+2] >> 2));
out[3] = (byte)(((in[off+2] << 5) & 0xFF) | (in[off+3] >> 3));
out[4] = (byte)(((in[off+3] << 4) & 0xFF) | (in[off+4] >> 4));
out[5] = (byte)(((in[off+4] << 3) & 0xFF) | (in[off+5] >> 5));
out[6] = (byte)(((in[off+5] << 2) & 0xFF) | (in[off+6] >> 6));
out[7] = (byte)((in[off+6] << 1) & 0xFF);
return out;
}
byte[] calcLMHash (byte[] pwb) {
byte[] magic = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25};
byte[] pwb1 = new byte [14];
int len = pwb.length;
if (len > 14)
len = 14;
System.arraycopy (pwb, 0, pwb1, 0, len); /* Zero padded */
try {
DESKeySpec dks1 = new DESKeySpec (makeDesKey (pwb1, 0));
DESKeySpec dks2 = new DESKeySpec (makeDesKey (pwb1, 7));
SecretKey key1 = fac.generateSecret (dks1);
SecretKey key2 = fac.generateSecret (dks2);
cipher.init (Cipher.ENCRYPT_MODE, key1);
byte[] out1 = cipher.doFinal (magic, 0, 8);
cipher.init (Cipher.ENCRYPT_MODE, key2);
byte[] out2 = cipher.doFinal (magic, 0, 8);
byte[] result = new byte [21];
System.arraycopy (out1, 0, result, 0, 8);
System.arraycopy (out2, 0, result, 8, 8);
return result;
} catch (InvalidKeyException ive) {
// Will not happen, all key material are 8 bytes
assert false;
} catch (InvalidKeySpecException ikse) {
// Will not happen, we only feed DESKeySpec to DES factory
assert false;
} catch (IllegalBlockSizeException ibse) {
// Will not happen, we encrypt 8 bytes
assert false;
} catch (BadPaddingException bpe) {
// Will not happen, this is encryption
assert false;
}
return null; // will not happen, we returned already
}
byte[] calcNTHash (byte[] pw) {
byte[] out = md4.digest (pw);
byte[] result = new byte [21];
System.arraycopy (out, 0, result, 0, 16);
return result;
}
/* key is a 21 byte array. Split it into 3 7 byte chunks,
* Convert each to 8 byte DES keys, encrypt the text arg with
* each key and return the three results in a sequential []
*/
byte[] calcResponse (byte[] key, byte[] text) {
try {
assert key.length == 21;
DESKeySpec dks1 = new DESKeySpec(makeDesKey(key, 0));
DESKeySpec dks2 = new DESKeySpec(makeDesKey(key, 7));
DESKeySpec dks3 = new DESKeySpec(makeDesKey(key, 14));
SecretKey key1 = fac.generateSecret(dks1);
SecretKey key2 = fac.generateSecret(dks2);
SecretKey key3 = fac.generateSecret(dks3);
cipher.init(Cipher.ENCRYPT_MODE, key1);
byte[] out1 = cipher.doFinal(text, 0, 8);
cipher.init(Cipher.ENCRYPT_MODE, key2);
byte[] out2 = cipher.doFinal(text, 0, 8);
cipher.init(Cipher.ENCRYPT_MODE, key3);
byte[] out3 = cipher.doFinal(text, 0, 8);
byte[] result = new byte[24];
System.arraycopy(out1, 0, result, 0, 8);
System.arraycopy(out2, 0, result, 8, 8);
System.arraycopy(out3, 0, result, 16, 8);
return result;
} catch (IllegalBlockSizeException ex) { // None will happen
assert false;
} catch (BadPaddingException ex) {
assert false;
} catch (InvalidKeySpecException ex) {
assert false;
} catch (InvalidKeyException ex) {
assert false;
}
return null;
}
// LMv2/NTLMv2
byte[] hmacMD5(byte[] key, byte[] text) {
try {
SecretKeySpec skey =
new SecretKeySpec(Arrays.copyOf(key, 16), "HmacMD5");
hmac.init(skey);
return hmac.doFinal(text);
} catch (InvalidKeyException ex) {
assert false;
} catch (RuntimeException e) {
assert false;
}
return null;
}
byte[] calcV2(byte[] nthash, String text, byte[] blob, byte[] challenge) {
try {
byte[] ntlmv2hash = hmacMD5(nthash,
text.getBytes("UnicodeLittleUnmarked"));
byte[] cn = new byte[blob.length+8];
System.arraycopy(challenge, 0, cn, 0, 8);
System.arraycopy(blob, 0, cn, 8, blob.length);
byte[] result = new byte[16+blob.length];
System.arraycopy(hmacMD5(ntlmv2hash, cn), 0, result, 0, 16);
System.arraycopy(blob, 0, result, 16, blob.length);
return result;
} catch (UnsupportedEncodingException ex) {
assert false;
}
return null;
}
// NTLM2 LM/NTLM
static byte[] ntlm2LM(byte[] nonce) {
return Arrays.copyOf(nonce, 24);
}
byte[] ntlm2NTLM(byte[] ntlmHash, byte[] nonce, byte[] challenge) {
byte[] b = Arrays.copyOf(challenge, 16);
System.arraycopy(nonce, 0, b, 8, 8);
byte[] sesshash = Arrays.copyOf(md5.digest(b), 8);
return calcResponse(ntlmHash, sesshash);
}
// Password in ASCII and UNICODE
static byte[] getP1(char[] password) {
try {
return new String(password).toUpperCase().getBytes("ISO8859_1");
} catch (UnsupportedEncodingException ex) {
return null;
}
}
static byte[] getP2(char[] password) {
try {
return new String(password).getBytes("UnicodeLittleUnmarked");
} catch (UnsupportedEncodingException ex) {
return null;
}
}
}
/*
* 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/
package com.sun.security.ntlm;
import java.security.GeneralSecurityException;
/**
* An NTLM-related Exception
*/
public final class NTLMException extends GeneralSecurityException {
/**
* If the incoming packet is invalid.
*/
public final static int PACKET_READ_ERROR = 1;
/**
* If the client cannot get a domain value from the server and the
* caller has not provided one.
*/
public final static int NO_DOMAIN_INFO = 2;
/**
* If the domain provided by the client does not match the one received
* from server.
*/
//public final static int DOMAIN_UNMATCH = 3;
/**
* If the client name is not found on server's user database.
*/
public final static int USER_UNKNOWN = 3;
/**
* If authentication fails.
*/
public final static int AUTH_FAILED = 4;
/**
* If an illegal version string is provided.
*/
public final static int BAD_VERSION = 5;
private int errorCode;
/**
* Constructs an NTLMException object.
* @param errorCode the error code, which can be retrieved by
* the {@link #errorCode() } method.
* @param msg the string message, which can be retrived by
* the {@link Exception#getMessage() } method.
*/
public NTLMException(int errorCode, String msg) {
super(msg);
this.errorCode = errorCode;
}
/**
* Returns the error code associated with this NTLMException.
* @return the error code
*/
public int errorCode() {
return errorCode;
}
}
/*
* 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/
package com.sun.security.ntlm;
import java.util.Arrays;
import java.util.Locale;
/**
* The NTLM server, not multi-thread enabled.<p>
* Example:
* <pre>
* Server server = new Server(null, "REALM") {
* public char[] getPassword(String ntdomain, String username) {
* switch (username) {
* case "dummy": return "t0pSeCr3t".toCharArray();
* case "guest": return "".toCharArray();
* default: return null;
* }
* }
* };
* // Receive client request as type1
* byte[] type2 = server.type2(type1, nonce);
* // Send type2 to client and receive type3
* verify(type3, nonce);
* </pre>
*/
public abstract class Server extends NTLM {
final private String domain;
final private boolean allVersion;
/**
* Creates a Server instance.
* @param version the NTLM version to use, which can be:
* <ul>
* <li>NTLM: Original NTLM v1
* <li>NTLM2: NTLM v1 with Client Challenge
* <li>NTLMv2: NTLM v2
* </ul>
* If null, all versions will be supported. Please note that unless NTLM2
* is selected, authentication succeeds if one of LM (or LMv2) or
* NTLM (or NTLMv2) is verified.
* @param domain the domain, must not be null
* @throws NullPointerException if {@code domain} is null.
*/
public Server(String version, String domain) throws NTLMException {
super(version);
if (domain == null) {
throw new NullPointerException("domain cannot be null");
}
this.allVersion = (version == null);
this.domain = domain;
debug("NTLM Server: (t,version) = (%s,%s)\n", domain, version);
}
/**
* Generates the Type 2 message
* @param type1 the Type1 message received, must not be null
* @param nonce the random 8-byte array to be used in message generation,
* must not be null
* @return the message generated
* @throws NullPointerException if type1 or nonce is null
* @throws NTLMException if the incoming message is invalid
*/
public byte[] type2(byte[] type1, byte[] nonce) {
if (nonce == null) {
throw new NullPointerException("nonce cannot be null");
}
debug("NTLM Server: Type 1 received\n");
if (type1 != null) debug(type1);
Writer p = new Writer(2, 32);
int flags = 0x80205;
p.writeSecurityBuffer(12, domain, true);
p.writeInt(20, flags);
p.writeBytes(24, nonce);
debug("NTLM Server: Type 2 created\n");
debug(p.getBytes());
return p.getBytes();
}
/**
* Verifies the Type3 message received from client and returns
* 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
* @throws NullPointerException if {@code type3} or {@code nonce} is null
* @throws NTLMException if the incoming message is invalid
*/
public String[] verify(byte[] type3, byte[] nonce)
throws NTLMException {
if (type3 == null || nonce == null) {
throw new NullPointerException("type1 or nonce cannot be null");
}
debug("NTLM Server: Type 3 received\n");
if (type3 != null) debug(type3);
Reader r = new Reader(type3);
String username = r.readSecurityBuffer(36, true);
String hostname = r.readSecurityBuffer(44, true);
String incomingDomain = r.readSecurityBuffer(28, true);
/*if (incomingDomain != null && !incomingDomain.equals(domain)) {
throw new NTLMException(NTLMException.DOMAIN_UNMATCH,
"Wrong domain: " + incomingDomain +
" vs " + domain); // Needed?
}*/
boolean verified = false;
char[] password = getPassword(domain, username);
if (password == null) {
throw new NTLMException(NTLMException.USER_UNKNOWN,
"Unknown user");
}
byte[] incomingLM = r.readSecurityBuffer(12);
byte[] incomingNTLM = r.readSecurityBuffer(20);
if (!verified && (allVersion || v == Version.NTLM)) {
if (incomingLM.length > 0) {
byte[] pw1 = getP1(password);
byte[] lmhash = calcLMHash(pw1);
byte[] lmresponse = calcResponse (lmhash, nonce);
if (Arrays.equals(lmresponse, incomingLM)) {
verified = true;
}
}
if (incomingNTLM.length > 0) {
byte[] pw2 = getP2(password);
byte[] nthash = calcNTHash(pw2);
byte[] ntresponse = calcResponse (nthash, nonce);
if (Arrays.equals(ntresponse, incomingNTLM)) {
verified = true;
}
}
debug("NTLM Server: verify using NTLM: " + verified + "\n");
}
if (!verified && (allVersion || v == Version.NTLM2)) {
byte[] pw2 = getP2(password);
byte[] nthash = calcNTHash(pw2);
byte[] clientNonce = Arrays.copyOf(incomingLM, 8);
byte[] ntlmresponse = ntlm2NTLM(nthash, clientNonce, nonce);
if (Arrays.equals(incomingNTLM, ntlmresponse)) {
verified = true;
}
debug("NTLM Server: verify using NTLM2: " + verified + "\n");
}
if (!verified && (allVersion || v == Version.NTLMv2)) {
byte[] pw2 = getP2(password);
byte[] nthash = calcNTHash(pw2);
if (incomingLM.length > 0) {
byte[] clientNonce = Arrays.copyOfRange(
incomingLM, 16, incomingLM.length);
byte[] lmresponse = calcV2(nthash,
username.toUpperCase(Locale.US)+incomingDomain,
clientNonce, nonce);
if (Arrays.equals(lmresponse, incomingLM)) {
verified = true;
}
}
if (incomingNTLM.length > 0) {
byte[] clientBlob = Arrays.copyOfRange(
incomingNTLM, 16, incomingNTLM.length);
byte[] ntlmresponse = calcV2(nthash,
username.toUpperCase(Locale.US)+incomingDomain,
clientBlob, nonce);
if (Arrays.equals(ntlmresponse, incomingNTLM)) {
verified = true;
}
}
debug("NTLM Server: verify using NTLMv2: " + verified + "\n");
}
if (!verified) {
throw new NTLMException(NTLMException.AUTH_FAILED,
"None of LM and NTLM verified");
}
return new String[] {username, hostname};
}
/**
* Retrieves the password for a given user. This method should be
* overridden in a concrete class.
* @param domain can be null
* @param username must not be null
* @return the password for the user, or null if unknown
*/
public abstract char[] getPassword(String domain, String username);
}
/*
* 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/
package com.sun.security.ntlm;
enum Version {
NTLM, NTLM2, NTLMv2
}
/*
* Copyright (c) 2003, 2006, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 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
......@@ -35,10 +35,12 @@ import java.security.PrivilegedAction;
* - CRAM-MD5
* - DIGEST-MD5
* - GSSAPI/Kerberos v5
* - NTLM
* And server support for
* - CRAM-MD5
* - DIGEST-MD5
* - GSSAPI/Kerberos v5
* - NTLM
*/
public final class Provider extends java.security.Provider {
......@@ -47,8 +49,8 @@ public final class Provider extends java.security.Provider {
private static final String info = "Sun SASL provider" +
"(implements client mechanisms for: " +
"DIGEST-MD5, GSSAPI, EXTERNAL, PLAIN, CRAM-MD5;" +
" server mechanisms for: DIGEST-MD5, GSSAPI, CRAM-MD5)";
"DIGEST-MD5, GSSAPI, EXTERNAL, PLAIN, CRAM-MD5, NTLM;" +
" server mechanisms for: DIGEST-MD5, GSSAPI, CRAM-MD5, NTLM)";
public Provider() {
super("SunSASL", 1.7d, info);
......@@ -58,6 +60,8 @@ public final class Provider extends java.security.Provider {
// Client mechanisms
put("SaslClientFactory.DIGEST-MD5",
"com.sun.security.sasl.digest.FactoryImpl");
put("SaslClientFactory.NTLM",
"com.sun.security.sasl.ntlm.FactoryImpl");
put("SaslClientFactory.GSSAPI",
"com.sun.security.sasl.gsskerb.FactoryImpl");
......@@ -75,6 +79,8 @@ public final class Provider extends java.security.Provider {
"com.sun.security.sasl.gsskerb.FactoryImpl");
put("SaslServerFactory.DIGEST-MD5",
"com.sun.security.sasl.digest.FactoryImpl");
put("SaslServerFactory.NTLM",
"com.sun.security.sasl.ntlm.FactoryImpl");
return null;
}
});
......
/*
* 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/
package com.sun.security.sasl.ntlm;
import java.util.Map;
import javax.security.sasl.*;
import javax.security.auth.callback.CallbackHandler;
import com.sun.security.sasl.util.PolicyUtils;
/**
* Client and server factory for NTLM SASL client/server mechanisms.
* See NTLMClient and NTLMServer for input requirements.
*
* @since 1.7
*/
public final class FactoryImpl implements SaslClientFactory,
SaslServerFactory{
private static final String myMechs[] = { "NTLM" };
private static final int mechPolicies[] = {
PolicyUtils.NOPLAINTEXT|PolicyUtils.NOANONYMOUS
};
/**
* Empty constructor.
*/
public FactoryImpl() {
}
/**
* Returns a new instance of the NTLM SASL client mechanism.
* Argument checks are performed in SaslClient's constructor.
* @returns a new SaslClient ; otherwise null if unsuccessful.
* @throws SaslException If there is an error creating the NTLM
* SASL client.
*/
public SaslClient createSaslClient(String[] mechs,
String authorizationId, String protocol, String serverName,
Map<String,?> props, CallbackHandler cbh)
throws SaslException {
for (int i=0; i<mechs.length; i++) {
if (mechs[i].equals("NTLM") &&
PolicyUtils.checkPolicy(mechPolicies[0], props)) {
return new NTLMClient(mechs[i], authorizationId,
protocol, serverName, props, cbh);
}
}
return null;
}
/**
* Returns a new instance of the NTLM SASL server mechanism.
* Argument checks are performed in SaslServer's constructor.
* @returns a new SaslServer ; otherwise null if unsuccessful.
* @throws SaslException If there is an error creating the NTLM
* SASL server.
*/
public SaslServer createSaslServer(String mech,
String protocol, String serverName, Map<String,?> props, CallbackHandler cbh)
throws SaslException {
if (mech.equals("NTLM") &&
PolicyUtils.checkPolicy(mechPolicies[0], props)) {
if (props != null) {
String qop = (String)props.get(Sasl.QOP);
if (qop != null && !qop.equals("auth")) {
throw new SaslException("NTLM only support auth");
}
}
if (cbh == null) {
throw new SaslException(
"Callback handler with support for AuthorizeCallback, "+
"RealmCallback, NameCallback, and PasswordCallback " +
"required");
}
return new NTLMServer(mech, protocol, serverName, props, cbh);
}
return null;
}
/**
* Returns the authentication mechanisms that this factory can produce.
*
* @returns String[] {"NTLM"} if policies in env match those of this
* factory.
*/
public String[] getMechanismNames(Map<String,?> env) {
return PolicyUtils.filterMechs(myMechs, mechPolicies, env);
}
}
/*
* 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/
package com.sun.security.sasl.ntlm;
import com.sun.security.ntlm.Client;
import com.sun.security.ntlm.NTLMException;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Map;
import java.util.Random;
import javax.security.auth.callback.Callback;
import javax.security.sasl.*;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
/**
* Required callbacks:
* - RealmCallback
* handle can provide domain info for authentication, optional
* - NameCallback
* handler must enter username to use for authentication
* - PasswordCallback
* handler must enter password for username to use for authentication
*
* Environment properties that affect behavior of implementation:
*
* javax.security.sasl.qop
* String, quality of protection; only "auth" is accepted, default "auth"
*
* com.sun.security.sasl.ntlm.version
* String, name a specific version to use; can be:
* LM/NTLM: Original NTLM v1
* LM: Original NTLM v1, LM only
* NTLM: Original NTLM v1, NTLM only
* NTLM2: NTLM v1 with Client Challenge
* LMv2/NTLMv2: NTLM v2
* LMv2: NTLM v2, LM only
* NTLMv2: NTLM v2, NTLM only
* If not specified, use system property "ntlm.version". If
* still not specified, use default value "LMv2/NTLMv2".
*
* com.sun.security.sasl.ntlm.random
* java.util.Random, the nonce source to be used in NTLM v2 or NTLM v1 with
* Client Challenge. Default null, an internal java.util.Random object
* will be used
*
* Negotiated Properties:
*
* javax.security.sasl.qop
* Always "auth"
*
* com.sun.security.sasl.html.domain
* The domain for the user, provided by the server
*
* @see <a href="http://www.ietf.org/rfc/rfc2222.txt">RFC 2222</a>
* - Simple Authentication and Security Layer (SASL)
*
*/
final class NTLMClient implements SaslClient {
private static final String NTLM_VERSION =
"com.sun.security.sasl.ntlm.version";
private static final String NTLM_RANDOM =
"com.sun.security.sasl.ntlm.random";
private final static String NTLM_DOMAIN =
"com.sun.security.sasl.ntlm.domain";
private final static String NTLM_HOSTNAME =
"com.sun.security.sasl.ntlm.hostname";
private final Client client;
private final String mech;
private final Random random;
private int step = 0; // 0-start,1-nego,2-auth,3-done
/**
* @param mech non-null
* @param authorizationId can be null or empty and ignored
* @param protocol non-null for Sasl, useless for NTLM
* @param serverName non-null for Sasl, but can be null for NTLM
* @param props can be null
* @param cbh can be null for Sasl, but will throw NPE for NTLM
* @throws SaslException
*/
NTLMClient(String mech, String authzid, String protocol, String serverName,
Map props, CallbackHandler cbh) throws SaslException {
this.mech = mech;
String version = null;
Random rtmp = null;
String hostname = null;
if (props != null) {
String qop = (String)props.get(Sasl.QOP);
if (qop != null && !qop.equals("auth")) {
throw new SaslException("NTLM only support auth");
}
version = (String)props.get(NTLM_VERSION);
rtmp = (Random)props.get(NTLM_RANDOM);
hostname = (String)props.get(NTLM_HOSTNAME);
}
this.random = rtmp != null ? rtmp : new Random();
if (version == null) {
version = System.getProperty("ntlm.version");
}
RealmCallback dcb = (serverName != null && !serverName.isEmpty())?
new RealmCallback("Realm: ", serverName) :
new RealmCallback("Realm: ");
NameCallback ncb = (authzid != null && !authzid.isEmpty()) ?
new NameCallback("User name: ", authzid) :
new NameCallback("User name: ");
PasswordCallback pcb =
new PasswordCallback("Password: ", false);
try {
cbh.handle(new Callback[] {dcb, ncb, pcb});
} catch (UnsupportedCallbackException e) {
throw new SaslException("NTLM: Cannot perform callback to " +
"acquire realm, username or password", e);
} catch (IOException e) {
throw new SaslException(
"NTLM: Error acquiring realm, username or password", e);
}
if (hostname == null) {
try {
hostname = InetAddress.getLocalHost().getCanonicalHostName();
} catch (UnknownHostException e) {
hostname = "localhost";
}
}
try {
client = new Client(version, hostname,
ncb.getName(),
dcb.getText(),
pcb.getPassword());
} catch (NTLMException ne) {
throw new SaslException(
"NTLM: Invalid version string: " + version, ne);
}
}
@Override
public String getMechanismName() {
return mech;
}
@Override
public boolean isComplete() {
return step >= 2;
}
@Override
public byte[] unwrap(byte[] incoming, int offset, int len)
throws SaslException {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public byte[] wrap(byte[] outgoing, int offset, int len)
throws SaslException {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public Object getNegotiatedProperty(String propName) {
if (propName.equals(Sasl.QOP)) {
return "auth";
} else if (propName.equals(NTLM_DOMAIN)) {
return client.getDomain();
} else {
return null;
}
}
@Override
public void dispose() throws SaslException {
client.dispose();
}
@Override
public boolean hasInitialResponse() {
return true;
}
@Override
public byte[] evaluateChallenge(byte[] challenge) throws SaslException {
step++;
if (step == 1) {
return client.type1();
} else {
try {
byte[] nonce = new byte[8];
random.nextBytes(nonce);
return client.type3(challenge, nonce);
} catch (NTLMException ex) {
throw new SaslException("Type3 creation failed", ex);
}
}
}
}
/*
* 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/
package com.sun.security.sasl.ntlm;
import com.sun.security.ntlm.NTLMException;
import com.sun.security.ntlm.Server;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Map;
import java.util.Random;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.sasl.*;
/**
* Required callbacks:
* - RealmCallback
* used as key by handler to fetch password, optional
* - NameCallback
* used as key by handler to fetch password
* - PasswordCallback
* handler must enter password for username/realm supplied
*
* Environment properties that affect the implementation:
*
* javax.security.sasl.qop
* String, quality of protection; only "auth" is accepted, default "auth"
*
* com.sun.security.sasl.ntlm.version
* String, name a specific version to accept:
* LM/NTLM: Original NTLM v1
* LM: Original NTLM v1, LM only
* NTLM: Original NTLM v1, NTLM only
* NTLM2: NTLM v1 with Client Challenge
* LMv2/NTLMv2: NTLM v2
* LMv2: NTLM v2, LM only
* NTLMv2: NTLM v2, NTLM only
* If not specified, use system property "ntlm.version". If also
* not specfied, all versions are accepted.
*
* com.sun.security.sasl.ntlm.domain
* String, the domain of the server, default is server name (fqdn parameter)
*
* com.sun.security.sasl.ntlm.random
* java.util.Random, the nonce source. Default null, an internal
* java.util.Random object will be used
*
* Negotiated Properties:
*
* javax.security.sasl.qop
* Always "auth"
*
* com.sun.security.sasl.ntlm.hostname
* The hostname for the user, provided by the client
*
*/
final class NTLMServer implements SaslServer {
private final static String NTLM_VERSION =
"com.sun.security.sasl.ntlm.version";
private final static String NTLM_DOMAIN =
"com.sun.security.sasl.ntlm.domain";
private final static String NTLM_HOSTNAME =
"com.sun.security.sasl.ntlm.hostname";
private static final String NTLM_RANDOM =
"com.sun.security.sasl.ntlm.random";
private final Random random;
private final Server server;
private byte[] nonce;
private int step = 0;
private String authzId;
private final String mech;
private String hostname;
/**
* @param mech not null
* @param protocol not null for Sasl, ignored in NTLM
* @param serverName not null for Sasl, can be null in NTLM. If non-null,
* might be used as domain if not provided in props
* @param props can be null
* @param cbh can be null for Sasl, but will throw NPE in auth for NTLM
* @throws SaslException
*/
NTLMServer(String mech, String protocol, String serverName,
Map props, final CallbackHandler cbh) throws SaslException {
this.mech = mech;
String version = null;
String domain = null;
Random rtmp = null;
if (props != null) {
domain = (String) props.get(NTLM_DOMAIN);
version = (String)props.get(NTLM_VERSION);
rtmp = (Random)props.get(NTLM_RANDOM);
}
random = rtmp != null ? rtmp : new Random();
if (version == null) {
version = System.getProperty("ntlm.version");
}
if (domain == null) {
domain = serverName;
}
if (domain == null) {
throw new NullPointerException("Domain must be provided as"
+ " the serverName argument or in props");
}
try {
server = new Server(version, domain) {
public char[] getPassword(String ntdomain, String username) {
try {
RealmCallback rcb = new RealmCallback(
"Domain: ", ntdomain);
NameCallback ncb = new NameCallback(
"Name: ", username);
PasswordCallback pcb = new PasswordCallback(
"Password: ", false);
cbh.handle(new Callback[] { rcb, ncb, pcb });
char[] passwd = pcb.getPassword();
pcb.clearPassword();
return passwd;
} catch (IOException ioe) {
return null;
} catch (UnsupportedCallbackException uce) {
return null;
}
}
};
} catch (NTLMException ne) {
throw new SaslException(
"NTLM: Invalid version string: " + version, ne);
}
nonce = new byte[8];
}
@Override
public String getMechanismName() {
return mech;
}
@Override
public byte[] evaluateResponse(byte[] response) throws SaslException {
try {
step++;
if (step == 1) {
random.nextBytes(nonce);
return server.type2(response, nonce);
} else {
String[] out = server.verify(response, nonce);
authzId = out[0];
hostname = out[1];
return null;
}
} catch (GeneralSecurityException ex) {
throw new SaslException("", ex);
}
}
@Override
public boolean isComplete() {
return step >= 2;
}
@Override
public String getAuthorizationID() {
return authzId;
}
@Override
public byte[] unwrap(byte[] incoming, int offset, int len)
throws SaslException {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public byte[] wrap(byte[] outgoing, int offset, int len)
throws SaslException {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public Object getNegotiatedProperty(String propName) {
if (propName.equals(Sasl.QOP)) {
return "auth";
} else if (propName.equals(NTLM_HOSTNAME)) {
return hostname;
} else {
return null;
}
}
@Override
public void dispose() throws SaslException {
return;
}
}
/*
* Copyright (c) 2005, 2008, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 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
......@@ -25,20 +25,14 @@
package sun.net.www.protocol.http.ntlm;
import com.sun.security.ntlm.Client;
import com.sun.security.ntlm.NTLMException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.PasswordAuthentication;
import java.net.UnknownHostException;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import sun.net.www.HeaderParser;
import sun.net.www.protocol.http.AuthenticationInfo;
......@@ -72,14 +66,8 @@ import sun.net.www.protocol.http.HttpURLConnection;
*/
public class NTLMAuthentication extends AuthenticationInfo {
private static final long serialVersionUID = -2403849171106437142L;
private static final long serialVersionUID = 170L;
private byte[] type1;
private byte[] type3;
private SecretKeyFactory fac;
private Cipher cipher;
private MessageDigest md4;
private String hostname;
private static String defaultDomain; /* Domain to use if not specified by user */
......@@ -94,53 +82,28 @@ public class NTLMAuthentication extends AuthenticationInfo {
}
private void init0() {
type1 = new byte[256];
type3 = new byte[256];
System.arraycopy (new byte[] {'N','T','L','M','S','S','P',0,1}, 0, type1, 0, 9);
type1[12] = (byte) 3;
type1[13] = (byte) 0xb2;
type1[28] = (byte) 0x20;
System.arraycopy (new byte[] {'N','T','L','M','S','S','P',0,3}, 0, type3, 0, 9);
type3[12] = (byte) 0x18;
type3[14] = (byte) 0x18;
type3[20] = (byte) 0x18;
type3[22] = (byte) 0x18;
type3[32] = (byte) 0x40;
type3[60] = (byte) 1;
type3[61] = (byte) 0x82;
try {
hostname = java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<String>() {
public String run() {
String localhost;
try {
localhost = InetAddress.getLocalHost().getHostName().toUpperCase();
} catch (UnknownHostException e) {
localhost = "localhost";
}
return localhost;
hostname = java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<String>() {
public String run() {
String localhost;
try {
localhost = InetAddress.getLocalHost().getHostName().toUpperCase();
} catch (UnknownHostException e) {
localhost = "localhost";
}
});
int x = hostname.indexOf ('.');
if (x != -1) {
hostname = hostname.substring (0, x);
return localhost;
}
fac = SecretKeyFactory.getInstance ("DES");
cipher = Cipher.getInstance ("DES/ECB/NoPadding");
md4 = sun.security.provider.MD4.getInstance();
} catch (NoSuchPaddingException e) {
assert false;
} catch (NoSuchAlgorithmException e) {
assert false;
});
int x = hostname.indexOf ('.');
if (x != -1) {
hostname = hostname.substring (0, x);
}
};
PasswordAuthentication pw;
String username;
String ntdomain;
String password;
Client client;
/**
* Create a NTLMAuthentication:
* Username may be specified as domain<BACKSLASH>username in the application Authenticator.
......@@ -156,6 +119,9 @@ public class NTLMAuthentication extends AuthenticationInfo {
}
private void init (PasswordAuthentication pw) {
String username;
String ntdomain;
char[] password;
this.pw = pw;
String s = pw.getUserName();
int i = s.indexOf ('\\');
......@@ -166,8 +132,19 @@ public class NTLMAuthentication extends AuthenticationInfo {
ntdomain = s.substring (0, i).toUpperCase();
username = s.substring (i+1);
}
password = new String (pw.getPassword());
password = pw.getPassword();
init0();
try {
client = new Client(System.getProperty("ntlm.version"), hostname,
username, ntdomain, password);
} catch (NTLMException ne) {
try {
client = new Client(null, hostname, username, ntdomain, password);
} catch (NTLMException ne2) {
// Will never happen
throw new AssertionError("Really?");
}
}
}
/**
......@@ -240,181 +217,26 @@ public class NTLMAuthentication extends AuthenticationInfo {
}
}
private void copybytes (byte[] dest, int destpos, String src, String enc) {
try {
byte[] x = src.getBytes(enc);
System.arraycopy (x, 0, dest, destpos, x.length);
} catch (UnsupportedEncodingException e) {
assert false;
}
}
private String buildType1Msg () {
int dlen = ntdomain.length();
type1[16]= (byte) (dlen % 256);
type1[17]= (byte) (dlen / 256);
type1[18] = type1[16];
type1[19] = type1[17];
int hlen = hostname.length();
type1[24]= (byte) (hlen % 256);
type1[25]= (byte) (hlen / 256);
type1[26] = type1[24];
type1[27] = type1[25];
copybytes (type1, 32, hostname, "ISO8859_1");
copybytes (type1, hlen+32, ntdomain, "ISO8859_1");
type1[20] = (byte) ((hlen+32) % 256);
type1[21] = (byte) ((hlen+32) / 256);
byte[] msg = new byte [32 + hlen + dlen];
System.arraycopy (type1, 0, msg, 0, 32 + hlen + dlen);
byte[] msg = client.type1();
String result = "NTLM " + (new B64Encoder()).encode (msg);
return result;
}
/* Convert a 7 byte array to an 8 byte array (for a des key with parity)
* input starts at offset off
*/
private byte[] makeDesKey (byte[] input, int off) {
int[] in = new int [input.length];
for (int i=0; i<in.length; i++ ) {
in[i] = input[i]<0 ? input[i]+256: input[i];
}
byte[] out = new byte[8];
out[0] = (byte)in[off+0];
out[1] = (byte)(((in[off+0] << 7) & 0xFF) | (in[off+1] >> 1));
out[2] = (byte)(((in[off+1] << 6) & 0xFF) | (in[off+2] >> 2));
out[3] = (byte)(((in[off+2] << 5) & 0xFF) | (in[off+3] >> 3));
out[4] = (byte)(((in[off+3] << 4) & 0xFF) | (in[off+4] >> 4));
out[5] = (byte)(((in[off+4] << 3) & 0xFF) | (in[off+5] >> 5));
out[6] = (byte)(((in[off+5] << 2) & 0xFF) | (in[off+6] >> 6));
out[7] = (byte)((in[off+6] << 1) & 0xFF);
return out;
}
private byte[] calcLMHash () throws GeneralSecurityException {
byte[] magic = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25};
byte[] pwb = password.toUpperCase ().getBytes();
byte[] pwb1 = new byte [14];
int len = password.length();
if (len > 14)
len = 14;
System.arraycopy (pwb, 0, pwb1, 0, len); /* Zero padded */
DESKeySpec dks1 = new DESKeySpec (makeDesKey (pwb1, 0));
DESKeySpec dks2 = new DESKeySpec (makeDesKey (pwb1, 7));
SecretKey key1 = fac.generateSecret (dks1);
SecretKey key2 = fac.generateSecret (dks2);
cipher.init (Cipher.ENCRYPT_MODE, key1);
byte[] out1 = cipher.doFinal (magic, 0, 8);
cipher.init (Cipher.ENCRYPT_MODE, key2);
byte[] out2 = cipher.doFinal (magic, 0, 8);
byte[] result = new byte [21];
System.arraycopy (out1, 0, result, 0, 8);
System.arraycopy (out2, 0, result, 8, 8);
return result;
}
private byte[] calcNTHash () throws GeneralSecurityException {
byte[] pw = null;
try {
pw = password.getBytes ("UnicodeLittleUnmarked");
} catch (UnsupportedEncodingException e) {
assert false;
}
byte[] out = md4.digest (pw);
byte[] result = new byte [21];
System.arraycopy (out, 0, result, 0, 16);
return result;
}
/* key is a 21 byte array. Split it into 3 7 byte chunks,
* Convert each to 8 byte DES keys, encrypt the text arg with
* each key and return the three results in a sequential []
*/
private byte[] calcResponse (byte[] key, byte[] text)
throws GeneralSecurityException {
assert key.length == 21;
DESKeySpec dks1 = new DESKeySpec (makeDesKey (key, 0));
DESKeySpec dks2 = new DESKeySpec (makeDesKey (key, 7));
DESKeySpec dks3 = new DESKeySpec (makeDesKey (key, 14));
SecretKey key1 = fac.generateSecret (dks1);
SecretKey key2 = fac.generateSecret (dks2);
SecretKey key3 = fac.generateSecret (dks3);
cipher.init (Cipher.ENCRYPT_MODE, key1);
byte[] out1 = cipher.doFinal (text, 0, 8);
cipher.init (Cipher.ENCRYPT_MODE, key2);
byte[] out2 = cipher.doFinal (text, 0, 8);
cipher.init (Cipher.ENCRYPT_MODE, key3);
byte[] out3 = cipher.doFinal (text, 0, 8);
byte[] result = new byte [24];
System.arraycopy (out1, 0, result, 0, 8);
System.arraycopy (out2, 0, result, 8, 8);
System.arraycopy (out3, 0, result, 16, 8);
return result;
}
private String buildType3Msg (String challenge) throws GeneralSecurityException,
IOException {
/* First decode the type2 message to get the server nonce */
/* nonce is located at type2[24] for 8 bytes */
byte[] type2 = (new sun.misc.BASE64Decoder()).decodeBuffer (challenge);
byte[] nonce = new byte [8];
System.arraycopy (type2, 24, nonce, 0, 8);
int ulen = username.length()*2;
type3[36] = type3[38] = (byte) (ulen % 256);
type3[37] = type3[39] = (byte) (ulen / 256);
int dlen = ntdomain.length()*2;
type3[28] = type3[30] = (byte) (dlen % 256);
type3[29] = type3[31] = (byte) (dlen / 256);
int hlen = hostname.length()*2;
type3[44] = type3[46] = (byte) (hlen % 256);
type3[45] = type3[47] = (byte) (hlen / 256);
int l = 64;
copybytes (type3, l, ntdomain, "UnicodeLittleUnmarked");
type3[32] = (byte) (l % 256);
type3[33] = (byte) (l / 256);
l += dlen;
copybytes (type3, l, username, "UnicodeLittleUnmarked");
type3[40] = (byte) (l % 256);
type3[41] = (byte) (l / 256);
l += ulen;
copybytes (type3, l, hostname, "UnicodeLittleUnmarked");
type3[48] = (byte) (l % 256);
type3[49] = (byte) (l / 256);
l += hlen;
byte[] lmhash = calcLMHash();
byte[] lmresponse = calcResponse (lmhash, nonce);
byte[] nthash = calcNTHash();
byte[] ntresponse = calcResponse (nthash, nonce);
System.arraycopy (lmresponse, 0, type3, l, 24);
type3[16] = (byte) (l % 256);
type3[17] = (byte) (l / 256);
l += 24;
System.arraycopy (ntresponse, 0, type3, l, 24);
type3[24] = (byte) (l % 256);
type3[25] = (byte) (l / 256);
l += 24;
type3[56] = (byte) (l % 256);
type3[57] = (byte) (l / 256);
byte[] msg = new byte [l];
System.arraycopy (type3, 0, msg, 0, l);
byte[] nonce = new byte[8];
new java.util.Random().nextBytes(nonce);
byte[] msg = client.type3(type2, nonce);
String result = "NTLM " + (new B64Encoder()).encode (msg);
return result;
}
}
class B64Encoder extends sun.misc.BASE64Encoder {
/* to force it to to the entire encoding in one line */
protected int bytesPerLine () {
......
/*
* 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();
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册