Mercurial > hg > jdk9-shenandoah > jdk
changeset 2728:9be643e70f42
6911951: NTLM should be a supported Java SASL mechanism
Reviewed-by: vinnie, michaelm
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/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/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/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/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/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/src/share/classes/com/sun/security/sasl/Provider.java Sat Aug 28 12:15:52 2010 -0700 +++ b/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/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/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/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/src/solaris/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java Sat Aug 28 12:15:52 2010 -0700 +++ b/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/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(); + } +}