6911951: NTLM should be a supported Java SASL mechanism
Reviewed-by: vinnie, michaelm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/security/ntlm/Client.java Mon Aug 30 14:37:43 2010 +0800
@@ -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);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/security/ntlm/NTLM.java Mon Aug 30 14:37:43 2010 +0800
@@ -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;
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/security/ntlm/NTLMException.java Mon Aug 30 14:37:43 2010 +0800
@@ -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;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/security/ntlm/Server.java Mon Aug 30 14:37:43 2010 +0800
@@ -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);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/security/ntlm/Version.java Mon Aug 30 14:37:43 2010 +0800
@@ -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
+}
--- a/jdk/src/share/classes/com/sun/security/sasl/Provider.java Sat Aug 28 12:15:52 2010 -0700
+++ b/jdk/src/share/classes/com/sun/security/sasl/Provider.java Mon Aug 30 14:37:43 2010 +0800
@@ -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 @@
* - 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 @@
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 @@
// 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 @@
"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;
}
});
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/security/sasl/ntlm/FactoryImpl.java Mon Aug 30 14:37:43 2010 +0800
@@ -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);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/security/sasl/ntlm/NTLMClient.java Mon Aug 30 14:37:43 2010 +0800
@@ -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);
+ }
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/security/sasl/ntlm/NTLMServer.java Mon Aug 30 14:37:43 2010 +0800
@@ -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;
+ }
+}
--- a/jdk/src/solaris/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java Sat Aug 28 12:15:52 2010 -0700
+++ b/jdk/src/solaris/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java Mon Aug 30 14:37:43 2010 +0800
@@ -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 @@
*/
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 @@
}
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 @@
}
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 @@
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 @@
}
}
- 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 () {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/com/sun/security/sasl/ntlm/NTLMTest.java Mon Aug 30 14:37:43 2010 +0800
@@ -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();
+ }
+}