KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > knowgate > jcifs > smb > NtlmPasswordAuthentication


1 /* jcifs smb client library in Java
2  * Copyright (C) 2002 "Michael B. Allen" <jcifs at samba dot org>
3  * "Eric Glass" <jcifs at samba dot org>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18  */

19
20 package com.knowgate.jcifs.smb;
21
22 import java.io.UnsupportedEncodingException JavaDoc;
23 import java.io.Serializable JavaDoc;
24 import java.security.Principal JavaDoc;
25 import java.util.Random JavaDoc;
26 import java.util.Arrays JavaDoc;
27
28 import com.knowgate.jcifs.util.DES;
29 import com.knowgate.jcifs.util.MD4;
30 import com.knowgate.jcifs.util.HMACT64;
31
32 import com.knowgate.debug.*;
33 import com.knowgate.jcifs.Config;
34
35 /**
36  * This class stores and encrypts NTLM user credentials. The default
37  * credentials are retrieved from the <tt>jcifs.smb.client.domain</tt>,
38  * <tt>jcifs.smb.client.username</tt>, and <tt>jcifs.smb.client.password</tt>
39  * properties.
40  * <p>
41  * Read <a HREF="../../../authhandler.html">jCIFS Exceptions and
42  * NtlmAuthenticator</a> for related information.
43  */

44
45 public final class NtlmPasswordAuthentication implements Principal JavaDoc, Serializable JavaDoc {
46
47     private static final int LM_COMPATIBILITY =
48             Config.getInt("jcifs.smb.lmCompatibility", 0);
49
50     private static final String JavaDoc DEFAULT_DOMAIN =
51             Config.getProperty("jcifs.smb.client.domain", "?");
52
53     private static final String JavaDoc DEFAULT_USERNAME =
54             Config.getProperty("jcifs.smb.client.username", "GUEST");
55
56     static final String JavaDoc DEFAULT_PASSWORD =
57             Config.getProperty("jcifs.smb.client.password", "");
58
59     private static final Random JavaDoc RANDOM = new Random JavaDoc();
60
61     // KGS!@#$%
62
private static final byte[] S8 = {
63         (byte)0x4b, (byte)0x47, (byte)0x53, (byte)0x21,
64         (byte)0x40, (byte)0x23, (byte)0x24, (byte)0x25
65     };
66     private static void E( byte[] key, byte[] data, byte[] e ) {
67         byte[] key7 = new byte[7];
68         byte[] e8 = new byte[8];
69
70         for( int i = 0; i < key.length / 7; i++ ) {
71             System.arraycopy( key, i * 7, key7, 0, 7 );
72             DES des = new DES( key7 );
73             des.encrypt( data, e8 );
74             System.arraycopy( e8, 0, e, i * 8, 8 );
75         }
76     }
77 /**
78  * Generate the ANSI DES hash for the password associated with these credentials.
79  */

80     static public byte[] getPreNTLMResponse( String JavaDoc password, byte[] challenge ) {
81         byte[] p14 = new byte[14];
82         byte[] p21 = new byte[21];
83         byte[] p24 = new byte[24];
84         byte[] passwordBytes;
85         try {
86             passwordBytes = password.toUpperCase().getBytes( ServerMessageBlock.OEM_ENCODING );
87         } catch( UnsupportedEncodingException JavaDoc uee ) {
88             return null;
89         }
90         int passwordLength = passwordBytes.length;
91
92         // Only encrypt the first 14 bytes of the password for Pre 0.12 NT LM
93
if( passwordLength > 14) {
94             passwordLength = 14;
95         }
96         System.arraycopy( passwordBytes, 0, p14, 0, passwordLength );
97         E( p14, S8, p21);
98         E( p21, challenge, p24);
99         return p24;
100     }
101 /**
102  * Generate the Unicode MD4 hash for the password associated with these credentials.
103  */

104     static public byte[] getNTLMResponse( String JavaDoc password, byte[] challenge ) {
105         byte[] uni = null;
106         byte[] p21 = new byte[21];
107         byte[] p24 = new byte[24];
108
109         try {
110             uni = password.getBytes( "UnicodeLittleUnmarked" );
111         } catch( UnsupportedEncodingException JavaDoc uee ) {
112             if( DebugFile.trace )
113                 new ErrorHandler(uee);
114         }
115         MD4 md4 = new MD4();
116         md4.update( uni );
117         try {
118             md4.digest(p21, 0, 16);
119         } catch (Exception JavaDoc ex) {
120             if( DebugFile.trace )
121                 new ErrorHandler(ex);
122         }
123         E( p21, challenge, p24 );
124         return p24;
125     }
126
127     /**
128      * Creates the LMv2 response for the supplied information.
129      *
130      * @param domain The domain in which the username exists.
131      * @param user The username.
132      * @param password The user's password.
133      * @param challenge The server challenge.
134      * @param clientChallenge The client challenge (nonce).
135      */

136     public static byte[] getLMv2Response(String JavaDoc domain, String JavaDoc user,
137             String JavaDoc password, byte[] challenge, byte[] clientChallenge) {
138         try {
139             byte[] hash = new byte[16];
140             byte[] response = new byte[24];
141             MD4 md4 = new MD4();
142             md4.update(password.getBytes("UnicodeLittleUnmarked"));
143             HMACT64 hmac = new HMACT64(md4.digest());
144             hmac.update(user.toUpperCase().getBytes("UnicodeLittleUnmarked"));
145             hmac.update(domain.toUpperCase().getBytes("UnicodeLittleUnmarked"));
146             hmac = new HMACT64(hmac.digest());
147             hmac.update(challenge);
148             hmac.update(clientChallenge);
149             hmac.digest(response, 0, 16);
150             System.arraycopy(clientChallenge, 0, response, 16, 8);
151             return response;
152         } catch (Exception JavaDoc ex) {
153             if( DebugFile.trace )
154                 new ErrorHandler(ex);
155             return null;
156         }
157     }
158
159     static final NtlmPasswordAuthentication NULL =
160                 new NtlmPasswordAuthentication( "", "", "" );
161     static final NtlmPasswordAuthentication GUEST =
162                 new NtlmPasswordAuthentication( "?", "GUEST", "" );
163     static final NtlmPasswordAuthentication DEFAULT =
164                 new NtlmPasswordAuthentication( null );
165
166     String JavaDoc domain;
167     String JavaDoc username;
168     String JavaDoc password;
169     byte[] ansiHash;
170     byte[] unicodeHash;
171     boolean hashesExternal = false;
172     byte[] clientChallenge = null;
173     byte[] challenge = null;
174
175 /**
176  * Create an <tt>NtlmPasswordAuthentication</tt> object from the userinfo
177  * component of an SMB URL like "<tt>domain;user:pass</tt>". This constructor
178  * is used internally be jCIFS when parsing SMB URLs.
179  */

180
181     public NtlmPasswordAuthentication( String JavaDoc userInfo ) {
182         domain = username = password = null;
183
184         if( userInfo != null ) {
185             int i, u, end;
186             char c;
187
188             end = userInfo.length();
189             for( i = 0, u = 0; i < end; i++ ) {
190                 c = userInfo.charAt( i );
191                 if( c == ';' ) {
192                     domain = userInfo.substring( 0, i );
193                     u = i + 1;
194                 } else if( c == ':' ) {
195                     password = userInfo.substring( i + 1 );
196                     break;
197                 }
198             }
199             username = userInfo.substring( u, i );
200         }
201
202         if( domain == null ) this.domain = DEFAULT_DOMAIN;
203         if( username == null ) this.username = DEFAULT_USERNAME;
204         if( password == null ) this.password = DEFAULT_PASSWORD;
205     }
206 /**
207  * Create an <tt>NtlmPasswordAuthentication</tt> object from a
208  * domain, username, and password. Parameters that are <tt>null</tt>
209  * will be substituted with <tt>jcifs.smb.client.domain</tt>,
210  * <tt>jcifs.smb.client.username</tt>, <tt>jcifs.smb.client.password</tt>
211  * property values.
212  */

213     public NtlmPasswordAuthentication( String JavaDoc domain, String JavaDoc username, String JavaDoc password ) {
214         this.domain = domain;
215         this.username = username;
216         this.password = password;
217         if( domain == null ) this.domain = DEFAULT_DOMAIN;
218         if( username == null ) this.username = DEFAULT_USERNAME;
219         if( password == null ) this.password = DEFAULT_PASSWORD;
220     }
221 /**
222  * Create an <tt>NtlmPasswordAuthentication</tt> object with raw password
223  * hashes. This is used exclusively by the <tt>jcifs.http.NtlmSsp</tt>
224  * class which is in turn used by NTLM HTTP authentication functionality.
225  */

226     public NtlmPasswordAuthentication( String JavaDoc domain, String JavaDoc username,
227                     byte[] challenge, byte[] ansiHash, byte[] unicodeHash ) {
228         if( domain == null || username == null ||
229                                     ansiHash == null || unicodeHash == null ) {
230             throw new IllegalArgumentException JavaDoc( "External credentials cannot be null" );
231         }
232         this.domain = domain;
233         this.username = username;
234         this.password = null;
235         this.challenge = challenge;
236         this.ansiHash = ansiHash;
237         this.unicodeHash = unicodeHash;
238         hashesExternal = true;
239     }
240
241 /**
242  * Returns the domain.
243  */

244     public String JavaDoc getDomain() {
245         return domain;
246     }
247 /**
248  * Returns the username.
249  */

250     public String JavaDoc getUsername() {
251         return username;
252     }
253 /**
254  * Returns the password in plain text or <tt>null</tt> if the raw password
255  * hashes were used to construct this <tt>NtlmPasswordAuthentication</tt>
256  * object which will be the case when NTLM HTTP Authentication is
257  * used. There is no way to retrieve a users password in plain text unless
258  * it is supplied by the user at runtime.
259  */

260     public String JavaDoc getPassword() {
261         return password;
262     }
263 /**
264  * Return the domain and username in the format:
265  * <tt>domain\\username</tt>. This is equivalent to <tt>toString()</tt>.
266  */

267     public String JavaDoc getName() {
268         boolean d = domain.length() > 0 && domain.equals( "?" ) == false;
269         return d ? domain + "\\" + username : username;
270     }
271
272 /**
273  * Computes the 24 byte ANSI password hash given the 8 byte server challenge.
274  */

275     public byte[] getAnsiHash( byte[] challenge ) {
276         if( hashesExternal ) {
277             return ansiHash;
278         }
279         switch (LM_COMPATIBILITY) {
280         case 0:
281         case 1:
282             return getPreNTLMResponse( password, challenge );
283         case 2:
284             return getNTLMResponse( password, challenge );
285         case 3:
286         case 4:
287         case 5:
288             if( clientChallenge == null ) {
289                 clientChallenge = new byte[8];
290                 RANDOM.nextBytes( clientChallenge );
291             }
292             return getLMv2Response(domain, username, password, challenge,
293                     clientChallenge);
294         default:
295             return getPreNTLMResponse( password, challenge );
296         }
297     }
298 /**
299  * Computes the 24 byte Unicode password hash given the 8 byte server challenge.
300  */

301     public byte[] getUnicodeHash( byte[] challenge ) {
302         if( hashesExternal ) {
303             return unicodeHash;
304         }
305         switch (LM_COMPATIBILITY) {
306         case 0:
307         case 1:
308         case 2:
309             return getNTLMResponse( password, challenge );
310         case 3:
311         case 4:
312         case 5:
313             /*
314             if( clientChallenge == null ) {
315                 clientChallenge = new byte[8];
316                 RANDOM.nextBytes( clientChallenge );
317             }
318             return getNTLMv2Response(domain, username, password, null,
319                     challenge, clientChallenge);
320             */

321             return new byte[0];
322         default:
323             return getNTLMResponse( password, challenge );
324         }
325     }
326
327     /**
328      * Returns the effective user session key.
329      *
330      * @param challenge The server challenge.
331      * @return A <code>byte[]</code> containing the effective user session key,
332      * used in SMB MAC signing and NTLMSSP signing and sealing.
333      */

334     public byte[] getUserSessionKey(byte[] challenge) {
335         if (hashesExternal) return null;
336         byte[] key = new byte[16];
337         try {
338             getUserSessionKey(challenge, key, 0);
339         } catch (Exception JavaDoc ex) {
340             if( DebugFile.trace )
341                 new ErrorHandler(ex);
342         }
343         return key;
344     }
345
346     /**
347      * Calculates the effective user session key.
348      *
349      * @param challenge The server challenge.
350      * @param dest The destination array in which the user session key will be
351      * placed.
352      * @param offset The offset in the destination array at which the
353      * session key will start.
354      */

355     void getUserSessionKey(byte[] challenge, byte[] dest, int offset)
356             throws Exception JavaDoc {
357         if (hashesExternal) return;
358         MD4 md4 = new MD4();
359         md4.update(password.getBytes("UnicodeLittleUnmarked"));
360         switch (LM_COMPATIBILITY) {
361         case 0:
362         case 1:
363         case 2:
364             md4.update(md4.digest());
365             md4.digest(dest, offset, 16);
366             break;
367         case 3:
368         case 4:
369         case 5:
370             if( clientChallenge == null ) {
371                 clientChallenge = new byte[8];
372                 RANDOM.nextBytes( clientChallenge );
373             }
374
375             HMACT64 hmac = new HMACT64(md4.digest());
376             hmac.update(username.toUpperCase().getBytes(
377                     "UnicodeLittleUnmarked"));
378             hmac.update(domain.toUpperCase().getBytes(
379                     "UnicodeLittleUnmarked"));
380             byte[] ntlmv2Hash = hmac.digest();
381             hmac = new HMACT64(ntlmv2Hash);
382             hmac.update(challenge);
383             hmac.update(clientChallenge);
384             HMACT64 userKey = new HMACT64(ntlmv2Hash);
385             userKey.update(hmac.digest());
386             userKey.digest(dest, offset, 16);
387             break;
388         default:
389             md4.update(md4.digest());
390             md4.digest(dest, offset, 16);
391             break;
392         }
393     }
394
395 /**
396  * Compares two <tt>NtlmPasswordAuthentication</tt> objects for
397  * equality. Two <tt>NtlmPasswordAuthentication</tt> objects are equal if
398  * 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.
399  */

400     public boolean equals( Object JavaDoc obj ) {
401         if( obj instanceof NtlmPasswordAuthentication ) {
402             NtlmPasswordAuthentication ntlm = (NtlmPasswordAuthentication)obj;
403             if( ntlm.domain.toUpperCase().equals( domain.toUpperCase() ) &&
404                         ntlm.username.toUpperCase().equals( username.toUpperCase() )) {
405                 if( hashesExternal && ntlm.hashesExternal ) {
406                     return Arrays.equals( ansiHash, ntlm.ansiHash ) &&
407                                 Arrays.equals( unicodeHash, ntlm.unicodeHash );
408                     /* This still isn't quite right. If one npa object does not have external
409                      * hashes and the other does then they will not be considered equal even
410                      * though they may be.
411                      */

412                 } else if( !hashesExternal && password.equals( ntlm.password )) {
413                     return true;
414                 }
415             }
416         }
417         return false;
418     }
419
420
421 /**
422  * Return the upcased username hash code.
423  */

424     public int hashCode() {
425         return getName().toUpperCase().hashCode();
426     }
427 /**
428  * Return the domain and username in the format:
429  * <tt>domain\\username</tt>. This is equivalent to <tt>getName()</tt>.
430  */

431     public String JavaDoc toString() {
432         return getName();
433     }
434 }
435
436
Popular Tags