mirror of
https://github.com/openjdk/jdk.git
synced 2026-03-24 06:40:05 +00:00
6911951: NTLM should be a supported Java SASL mechanism
Reviewed-by: vinnie, michaelm
This commit is contained in:
parent
aff3ad21b8
commit
7fe5113fe7
212
jdk/src/share/classes/com/sun/security/ntlm/Client.java
Normal file
212
jdk/src/share/classes/com/sun/security/ntlm/Client.java
Normal file
@ -0,0 +1,212 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
426
jdk/src/share/classes/com/sun/security/ntlm/NTLM.java
Normal file
426
jdk/src/share/classes/com/sun/security/ntlm/NTLM.java
Normal file
@ -0,0 +1,426 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
205
jdk/src/share/classes/com/sun/security/ntlm/Server.java
Normal file
205
jdk/src/share/classes/com/sun/security/ntlm/Server.java
Normal file
@ -0,0 +1,205 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
30
jdk/src/share/classes/com/sun/security/ntlm/Version.java
Normal file
30
jdk/src/share/classes/com/sun/security/ntlm/Version.java
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
});
|
||||
|
||||
@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
231
jdk/src/share/classes/com/sun/security/sasl/ntlm/NTLMClient.java
Normal file
231
jdk/src/share/classes/com/sun/security/sasl/ntlm/NTLMClient.java
Normal file
@ -0,0 +1,231 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
226
jdk/src/share/classes/com/sun/security/sasl/ntlm/NTLMServer.java
Normal file
226
jdk/src/share/classes/com/sun/security/sasl/ntlm/NTLMServer.java
Normal file
@ -0,0 +1,226 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* 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 () {
|
||||
|
||||
416
jdk/test/com/sun/security/sasl/ntlm/NTLMTest.java
Normal file
416
jdk/test/com/sun/security/sasl/ntlm/NTLMTest.java
Normal file
@ -0,0 +1,416 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user