001: /* jcifs smb client library in Java
002: * Copyright (C) 2002 "Michael B. Allen" <jcifs at samba dot org>
003: * "Eric Glass" <jcifs at samba dot org>
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: */
019:
020: package com.knowgate.jcifs.smb;
021:
022: import java.io.UnsupportedEncodingException;
023: import java.io.Serializable;
024: import java.security.Principal;
025: import java.util.Random;
026: import java.util.Arrays;
027:
028: import com.knowgate.jcifs.util.DES;
029: import com.knowgate.jcifs.util.MD4;
030: import com.knowgate.jcifs.util.HMACT64;
031:
032: import com.knowgate.debug.*;
033: import com.knowgate.jcifs.Config;
034:
035: /**
036: * This class stores and encrypts NTLM user credentials. The default
037: * credentials are retrieved from the <tt>jcifs.smb.client.domain</tt>,
038: * <tt>jcifs.smb.client.username</tt>, and <tt>jcifs.smb.client.password</tt>
039: * properties.
040: * <p>
041: * Read <a href="../../../authhandler.html">jCIFS Exceptions and
042: * NtlmAuthenticator</a> for related information.
043: */
044:
045: public final class NtlmPasswordAuthentication implements Principal,
046: Serializable {
047:
048: private static final int LM_COMPATIBILITY = Config.getInt(
049: "jcifs.smb.lmCompatibility", 0);
050:
051: private static final String DEFAULT_DOMAIN = Config.getProperty(
052: "jcifs.smb.client.domain", "?");
053:
054: private static final String DEFAULT_USERNAME = Config.getProperty(
055: "jcifs.smb.client.username", "GUEST");
056:
057: static final String DEFAULT_PASSWORD = Config.getProperty(
058: "jcifs.smb.client.password", "");
059:
060: private static final Random RANDOM = new Random();
061:
062: // KGS!@#$%
063: private static final byte[] S8 = { (byte) 0x4b, (byte) 0x47,
064: (byte) 0x53, (byte) 0x21, (byte) 0x40, (byte) 0x23,
065: (byte) 0x24, (byte) 0x25 };
066:
067: private static void E(byte[] key, byte[] data, byte[] e) {
068: byte[] key7 = new byte[7];
069: byte[] e8 = new byte[8];
070:
071: for (int i = 0; i < key.length / 7; i++) {
072: System.arraycopy(key, i * 7, key7, 0, 7);
073: DES des = new DES(key7);
074: des.encrypt(data, e8);
075: System.arraycopy(e8, 0, e, i * 8, 8);
076: }
077: }
078:
079: /**
080: * Generate the ANSI DES hash for the password associated with these credentials.
081: */
082: static public byte[] getPreNTLMResponse(String password,
083: byte[] challenge) {
084: byte[] p14 = new byte[14];
085: byte[] p21 = new byte[21];
086: byte[] p24 = new byte[24];
087: byte[] passwordBytes;
088: try {
089: passwordBytes = password.toUpperCase().getBytes(
090: ServerMessageBlock.OEM_ENCODING);
091: } catch (UnsupportedEncodingException uee) {
092: return null;
093: }
094: int passwordLength = passwordBytes.length;
095:
096: // Only encrypt the first 14 bytes of the password for Pre 0.12 NT LM
097: if (passwordLength > 14) {
098: passwordLength = 14;
099: }
100: System.arraycopy(passwordBytes, 0, p14, 0, passwordLength);
101: E(p14, S8, p21);
102: E(p21, challenge, p24);
103: return p24;
104: }
105:
106: /**
107: * Generate the Unicode MD4 hash for the password associated with these credentials.
108: */
109: static public byte[] getNTLMResponse(String password,
110: byte[] challenge) {
111: byte[] uni = null;
112: byte[] p21 = new byte[21];
113: byte[] p24 = new byte[24];
114:
115: try {
116: uni = password.getBytes("UnicodeLittleUnmarked");
117: } catch (UnsupportedEncodingException uee) {
118: if (DebugFile.trace)
119: new ErrorHandler(uee);
120: }
121: MD4 md4 = new MD4();
122: md4.update(uni);
123: try {
124: md4.digest(p21, 0, 16);
125: } catch (Exception ex) {
126: if (DebugFile.trace)
127: new ErrorHandler(ex);
128: }
129: E(p21, challenge, p24);
130: return p24;
131: }
132:
133: /**
134: * Creates the LMv2 response for the supplied information.
135: *
136: * @param domain The domain in which the username exists.
137: * @param user The username.
138: * @param password The user's password.
139: * @param challenge The server challenge.
140: * @param clientChallenge The client challenge (nonce).
141: */
142: public static byte[] getLMv2Response(String domain, String user,
143: String password, byte[] challenge, byte[] clientChallenge) {
144: try {
145: byte[] hash = new byte[16];
146: byte[] response = new byte[24];
147: MD4 md4 = new MD4();
148: md4.update(password.getBytes("UnicodeLittleUnmarked"));
149: HMACT64 hmac = new HMACT64(md4.digest());
150: hmac.update(user.toUpperCase().getBytes(
151: "UnicodeLittleUnmarked"));
152: hmac.update(domain.toUpperCase().getBytes(
153: "UnicodeLittleUnmarked"));
154: hmac = new HMACT64(hmac.digest());
155: hmac.update(challenge);
156: hmac.update(clientChallenge);
157: hmac.digest(response, 0, 16);
158: System.arraycopy(clientChallenge, 0, response, 16, 8);
159: return response;
160: } catch (Exception ex) {
161: if (DebugFile.trace)
162: new ErrorHandler(ex);
163: return null;
164: }
165: }
166:
167: static final NtlmPasswordAuthentication NULL = new NtlmPasswordAuthentication(
168: "", "", "");
169: static final NtlmPasswordAuthentication GUEST = new NtlmPasswordAuthentication(
170: "?", "GUEST", "");
171: static final NtlmPasswordAuthentication DEFAULT = new NtlmPasswordAuthentication(
172: null);
173:
174: String domain;
175: String username;
176: String password;
177: byte[] ansiHash;
178: byte[] unicodeHash;
179: boolean hashesExternal = false;
180: byte[] clientChallenge = null;
181: byte[] challenge = null;
182:
183: /**
184: * Create an <tt>NtlmPasswordAuthentication</tt> object from the userinfo
185: * component of an SMB URL like "<tt>domain;user:pass</tt>". This constructor
186: * is used internally be jCIFS when parsing SMB URLs.
187: */
188:
189: public NtlmPasswordAuthentication(String userInfo) {
190: domain = username = password = null;
191:
192: if (userInfo != null) {
193: int i, u, end;
194: char c;
195:
196: end = userInfo.length();
197: for (i = 0, u = 0; i < end; i++) {
198: c = userInfo.charAt(i);
199: if (c == ';') {
200: domain = userInfo.substring(0, i);
201: u = i + 1;
202: } else if (c == ':') {
203: password = userInfo.substring(i + 1);
204: break;
205: }
206: }
207: username = userInfo.substring(u, i);
208: }
209:
210: if (domain == null)
211: this .domain = DEFAULT_DOMAIN;
212: if (username == null)
213: this .username = DEFAULT_USERNAME;
214: if (password == null)
215: this .password = DEFAULT_PASSWORD;
216: }
217:
218: /**
219: * Create an <tt>NtlmPasswordAuthentication</tt> object from a
220: * domain, username, and password. Parameters that are <tt>null</tt>
221: * will be substituted with <tt>jcifs.smb.client.domain</tt>,
222: * <tt>jcifs.smb.client.username</tt>, <tt>jcifs.smb.client.password</tt>
223: * property values.
224: */
225: public NtlmPasswordAuthentication(String domain, String username,
226: String password) {
227: this .domain = domain;
228: this .username = username;
229: this .password = password;
230: if (domain == null)
231: this .domain = DEFAULT_DOMAIN;
232: if (username == null)
233: this .username = DEFAULT_USERNAME;
234: if (password == null)
235: this .password = DEFAULT_PASSWORD;
236: }
237:
238: /**
239: * Create an <tt>NtlmPasswordAuthentication</tt> object with raw password
240: * hashes. This is used exclusively by the <tt>jcifs.http.NtlmSsp</tt>
241: * class which is in turn used by NTLM HTTP authentication functionality.
242: */
243: public NtlmPasswordAuthentication(String domain, String username,
244: byte[] challenge, byte[] ansiHash, byte[] unicodeHash) {
245: if (domain == null || username == null || ansiHash == null
246: || unicodeHash == null) {
247: throw new IllegalArgumentException(
248: "External credentials cannot be null");
249: }
250: this .domain = domain;
251: this .username = username;
252: this .password = null;
253: this .challenge = challenge;
254: this .ansiHash = ansiHash;
255: this .unicodeHash = unicodeHash;
256: hashesExternal = true;
257: }
258:
259: /**
260: * Returns the domain.
261: */
262: public String getDomain() {
263: return domain;
264: }
265:
266: /**
267: * Returns the username.
268: */
269: public String getUsername() {
270: return username;
271: }
272:
273: /**
274: * Returns the password in plain text or <tt>null</tt> if the raw password
275: * hashes were used to construct this <tt>NtlmPasswordAuthentication</tt>
276: * object which will be the case when NTLM HTTP Authentication is
277: * used. There is no way to retrieve a users password in plain text unless
278: * it is supplied by the user at runtime.
279: */
280: public String getPassword() {
281: return password;
282: }
283:
284: /**
285: * Return the domain and username in the format:
286: * <tt>domain\\username</tt>. This is equivalent to <tt>toString()</tt>.
287: */
288: public String getName() {
289: boolean d = domain.length() > 0 && domain.equals("?") == false;
290: return d ? domain + "\\" + username : username;
291: }
292:
293: /**
294: * Computes the 24 byte ANSI password hash given the 8 byte server challenge.
295: */
296: public byte[] getAnsiHash(byte[] challenge) {
297: if (hashesExternal) {
298: return ansiHash;
299: }
300: switch (LM_COMPATIBILITY) {
301: case 0:
302: case 1:
303: return getPreNTLMResponse(password, challenge);
304: case 2:
305: return getNTLMResponse(password, challenge);
306: case 3:
307: case 4:
308: case 5:
309: if (clientChallenge == null) {
310: clientChallenge = new byte[8];
311: RANDOM.nextBytes(clientChallenge);
312: }
313: return getLMv2Response(domain, username, password,
314: challenge, clientChallenge);
315: default:
316: return getPreNTLMResponse(password, challenge);
317: }
318: }
319:
320: /**
321: * Computes the 24 byte Unicode password hash given the 8 byte server challenge.
322: */
323: public byte[] getUnicodeHash(byte[] challenge) {
324: if (hashesExternal) {
325: return unicodeHash;
326: }
327: switch (LM_COMPATIBILITY) {
328: case 0:
329: case 1:
330: case 2:
331: return getNTLMResponse(password, challenge);
332: case 3:
333: case 4:
334: case 5:
335: /*
336: if( clientChallenge == null ) {
337: clientChallenge = new byte[8];
338: RANDOM.nextBytes( clientChallenge );
339: }
340: return getNTLMv2Response(domain, username, password, null,
341: challenge, clientChallenge);
342: */
343: return new byte[0];
344: default:
345: return getNTLMResponse(password, challenge);
346: }
347: }
348:
349: /**
350: * Returns the effective user session key.
351: *
352: * @param challenge The server challenge.
353: * @return A <code>byte[]</code> containing the effective user session key,
354: * used in SMB MAC signing and NTLMSSP signing and sealing.
355: */
356: public byte[] getUserSessionKey(byte[] challenge) {
357: if (hashesExternal)
358: return null;
359: byte[] key = new byte[16];
360: try {
361: getUserSessionKey(challenge, key, 0);
362: } catch (Exception ex) {
363: if (DebugFile.trace)
364: new ErrorHandler(ex);
365: }
366: return key;
367: }
368:
369: /**
370: * Calculates the effective user session key.
371: *
372: * @param challenge The server challenge.
373: * @param dest The destination array in which the user session key will be
374: * placed.
375: * @param offset The offset in the destination array at which the
376: * session key will start.
377: */
378: void getUserSessionKey(byte[] challenge, byte[] dest, int offset)
379: throws Exception {
380: if (hashesExternal)
381: return;
382: MD4 md4 = new MD4();
383: md4.update(password.getBytes("UnicodeLittleUnmarked"));
384: switch (LM_COMPATIBILITY) {
385: case 0:
386: case 1:
387: case 2:
388: md4.update(md4.digest());
389: md4.digest(dest, offset, 16);
390: break;
391: case 3:
392: case 4:
393: case 5:
394: if (clientChallenge == null) {
395: clientChallenge = new byte[8];
396: RANDOM.nextBytes(clientChallenge);
397: }
398:
399: HMACT64 hmac = new HMACT64(md4.digest());
400: hmac.update(username.toUpperCase().getBytes(
401: "UnicodeLittleUnmarked"));
402: hmac.update(domain.toUpperCase().getBytes(
403: "UnicodeLittleUnmarked"));
404: byte[] ntlmv2Hash = hmac.digest();
405: hmac = new HMACT64(ntlmv2Hash);
406: hmac.update(challenge);
407: hmac.update(clientChallenge);
408: HMACT64 userKey = new HMACT64(ntlmv2Hash);
409: userKey.update(hmac.digest());
410: userKey.digest(dest, offset, 16);
411: break;
412: default:
413: md4.update(md4.digest());
414: md4.digest(dest, offset, 16);
415: break;
416: }
417: }
418:
419: /**
420: * Compares two <tt>NtlmPasswordAuthentication</tt> objects for
421: * equality. Two <tt>NtlmPasswordAuthentication</tt> objects are equal if
422: * their caseless domain and username fields are equal and either both hashes are external and they are equal or both internally supplied passwords are equal. If one <tt>NtlmPasswordAuthentication</tt> object has external hashes (meaning negotiated via NTLM HTTP Authentication) and the other does not they will not be equal. This is technically not correct however the server 8 byte challage would be required to compute and compare the password hashes but that it not available with this method.
423: */
424: public boolean equals(Object obj) {
425: if (obj instanceof NtlmPasswordAuthentication) {
426: NtlmPasswordAuthentication ntlm = (NtlmPasswordAuthentication) obj;
427: if (ntlm.domain.toUpperCase().equals(domain.toUpperCase())
428: && ntlm.username.toUpperCase().equals(
429: username.toUpperCase())) {
430: if (hashesExternal && ntlm.hashesExternal) {
431: return Arrays.equals(ansiHash, ntlm.ansiHash)
432: && Arrays.equals(unicodeHash,
433: ntlm.unicodeHash);
434: /* This still isn't quite right. If one npa object does not have external
435: * hashes and the other does then they will not be considered equal even
436: * though they may be.
437: */
438: } else if (!hashesExternal
439: && password.equals(ntlm.password)) {
440: return true;
441: }
442: }
443: }
444: return false;
445: }
446:
447: /**
448: * Return the upcased username hash code.
449: */
450: public int hashCode() {
451: return getName().toUpperCase().hashCode();
452: }
453:
454: /**
455: * Return the domain and username in the format:
456: * <tt>domain\\username</tt>. This is equivalent to <tt>getName()</tt>.
457: */
458: public String toString() {
459: return getName();
460: }
461: }
|