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