|
1 /* |
|
2 * ==================================================================== |
|
3 * |
|
4 * Licensed to the Apache Software Foundation (ASF) under one or more |
|
5 * contributor license agreements. See the NOTICE file distributed with |
|
6 * this work for additional information regarding copyright ownership. |
|
7 * The ASF licenses this file to You under the Apache License, Version 2.0 |
|
8 * (the "License"); you may not use this file except in compliance with |
|
9 * the License. You may obtain a copy of the License at |
|
10 * |
|
11 * http://www.apache.org/licenses/LICENSE-2.0 |
|
12 * |
|
13 * Unless required by applicable law or agreed to in writing, software |
|
14 * distributed under the License is distributed on an "AS IS" BASIS, |
|
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
16 * See the License for the specific language governing permissions and |
|
17 * limitations under the License. |
|
18 * ==================================================================== |
|
19 * |
|
20 * This software consists of voluntary contributions made by many |
|
21 * individuals on behalf of the Apache Software Foundation. For more |
|
22 * information on the Apache Software Foundation, please see |
|
23 * <http://www.apache.org/>. |
|
24 * |
|
25 */ |
|
26 |
|
27 package ch.boye.httpclientandroidlib.impl.auth; |
|
28 |
|
29 import java.security.Key; |
|
30 import java.security.MessageDigest; |
|
31 import java.util.Arrays; |
|
32 |
|
33 import javax.crypto.Cipher; |
|
34 import javax.crypto.spec.SecretKeySpec; |
|
35 |
|
36 import org.mozilla.apache.commons.codec.binary.Base64; |
|
37 import ch.boye.httpclientandroidlib.util.EncodingUtils; |
|
38 |
|
39 /** |
|
40 * Provides an implementation for NTLMv1, NTLMv2, and NTLM2 Session forms of the NTLM |
|
41 * authentication protocol. |
|
42 * |
|
43 * @since 4.1 |
|
44 */ |
|
45 final class NTLMEngineImpl implements NTLMEngine { |
|
46 |
|
47 // Flags we use |
|
48 protected final static int FLAG_UNICODE_ENCODING = 0x00000001; |
|
49 protected final static int FLAG_TARGET_DESIRED = 0x00000004; |
|
50 protected final static int FLAG_NEGOTIATE_SIGN = 0x00000010; |
|
51 protected final static int FLAG_NEGOTIATE_SEAL = 0x00000020; |
|
52 protected final static int FLAG_NEGOTIATE_NTLM = 0x00000200; |
|
53 protected final static int FLAG_NEGOTIATE_ALWAYS_SIGN = 0x00008000; |
|
54 protected final static int FLAG_NEGOTIATE_NTLM2 = 0x00080000; |
|
55 protected final static int FLAG_NEGOTIATE_128 = 0x20000000; |
|
56 protected final static int FLAG_NEGOTIATE_KEY_EXCH = 0x40000000; |
|
57 |
|
58 /** Secure random generator */ |
|
59 private static final java.security.SecureRandom RND_GEN; |
|
60 static { |
|
61 java.security.SecureRandom rnd = null; |
|
62 try { |
|
63 rnd = java.security.SecureRandom.getInstance("SHA1PRNG"); |
|
64 } catch (Exception e) { |
|
65 } |
|
66 RND_GEN = rnd; |
|
67 } |
|
68 |
|
69 /** Character encoding */ |
|
70 static final String DEFAULT_CHARSET = "ASCII"; |
|
71 |
|
72 /** The character set to use for encoding the credentials */ |
|
73 private String credentialCharset = DEFAULT_CHARSET; |
|
74 |
|
75 /** The signature string as bytes in the default encoding */ |
|
76 private static byte[] SIGNATURE; |
|
77 |
|
78 static { |
|
79 byte[] bytesWithoutNull = EncodingUtils.getBytes("NTLMSSP", "ASCII"); |
|
80 SIGNATURE = new byte[bytesWithoutNull.length + 1]; |
|
81 System.arraycopy(bytesWithoutNull, 0, SIGNATURE, 0, bytesWithoutNull.length); |
|
82 SIGNATURE[bytesWithoutNull.length] = (byte) 0x00; |
|
83 } |
|
84 |
|
85 /** |
|
86 * Returns the response for the given message. |
|
87 * |
|
88 * @param message |
|
89 * the message that was received from the server. |
|
90 * @param username |
|
91 * the username to authenticate with. |
|
92 * @param password |
|
93 * the password to authenticate with. |
|
94 * @param host |
|
95 * The host. |
|
96 * @param domain |
|
97 * the NT domain to authenticate in. |
|
98 * @return The response. |
|
99 * @throws HttpException |
|
100 * If the messages cannot be retrieved. |
|
101 */ |
|
102 final String getResponseFor(String message, String username, String password, |
|
103 String host, String domain) throws NTLMEngineException { |
|
104 |
|
105 final String response; |
|
106 if (message == null || message.trim().equals("")) { |
|
107 response = getType1Message(host, domain); |
|
108 } else { |
|
109 Type2Message t2m = new Type2Message(message); |
|
110 response = getType3Message(username, password, host, domain, t2m.getChallenge(), t2m |
|
111 .getFlags(), t2m.getTarget(), t2m.getTargetInfo()); |
|
112 } |
|
113 return response; |
|
114 } |
|
115 |
|
116 /** |
|
117 * Creates the first message (type 1 message) in the NTLM authentication |
|
118 * sequence. This message includes the user name, domain and host for the |
|
119 * authentication session. |
|
120 * |
|
121 * @param host |
|
122 * the computer name of the host requesting authentication. |
|
123 * @param domain |
|
124 * The domain to authenticate with. |
|
125 * @return String the message to add to the HTTP request header. |
|
126 */ |
|
127 String getType1Message(String host, String domain) throws NTLMEngineException { |
|
128 return new Type1Message(domain, host).getResponse(); |
|
129 } |
|
130 |
|
131 /** |
|
132 * Creates the type 3 message using the given server nonce. The type 3 |
|
133 * message includes all the information for authentication, host, domain, |
|
134 * username and the result of encrypting the nonce sent by the server using |
|
135 * the user's password as the key. |
|
136 * |
|
137 * @param user |
|
138 * The user name. This should not include the domain name. |
|
139 * @param password |
|
140 * The password. |
|
141 * @param host |
|
142 * The host that is originating the authentication request. |
|
143 * @param domain |
|
144 * The domain to authenticate within. |
|
145 * @param nonce |
|
146 * the 8 byte array the server sent. |
|
147 * @return The type 3 message. |
|
148 * @throws NTLMEngineException |
|
149 * If {@encrypt(byte[],byte[])} fails. |
|
150 */ |
|
151 String getType3Message(String user, String password, String host, String domain, |
|
152 byte[] nonce, int type2Flags, String target, byte[] targetInformation) |
|
153 throws NTLMEngineException { |
|
154 return new Type3Message(domain, host, user, password, nonce, type2Flags, target, |
|
155 targetInformation).getResponse(); |
|
156 } |
|
157 |
|
158 /** |
|
159 * @return Returns the credentialCharset. |
|
160 */ |
|
161 String getCredentialCharset() { |
|
162 return credentialCharset; |
|
163 } |
|
164 |
|
165 /** |
|
166 * @param credentialCharset |
|
167 * The credentialCharset to set. |
|
168 */ |
|
169 void setCredentialCharset(String credentialCharset) { |
|
170 this.credentialCharset = credentialCharset; |
|
171 } |
|
172 |
|
173 /** Strip dot suffix from a name */ |
|
174 private static String stripDotSuffix(String value) { |
|
175 int index = value.indexOf("."); |
|
176 if (index != -1) |
|
177 return value.substring(0, index); |
|
178 return value; |
|
179 } |
|
180 |
|
181 /** Convert host to standard form */ |
|
182 private static String convertHost(String host) { |
|
183 return stripDotSuffix(host); |
|
184 } |
|
185 |
|
186 /** Convert domain to standard form */ |
|
187 private static String convertDomain(String domain) { |
|
188 return stripDotSuffix(domain); |
|
189 } |
|
190 |
|
191 private static int readULong(byte[] src, int index) throws NTLMEngineException { |
|
192 if (src.length < index + 4) |
|
193 throw new NTLMEngineException("NTLM authentication - buffer too small for DWORD"); |
|
194 return (src[index] & 0xff) | ((src[index + 1] & 0xff) << 8) |
|
195 | ((src[index + 2] & 0xff) << 16) | ((src[index + 3] & 0xff) << 24); |
|
196 } |
|
197 |
|
198 private static int readUShort(byte[] src, int index) throws NTLMEngineException { |
|
199 if (src.length < index + 2) |
|
200 throw new NTLMEngineException("NTLM authentication - buffer too small for WORD"); |
|
201 return (src[index] & 0xff) | ((src[index + 1] & 0xff) << 8); |
|
202 } |
|
203 |
|
204 private static byte[] readSecurityBuffer(byte[] src, int index) throws NTLMEngineException { |
|
205 int length = readUShort(src, index); |
|
206 int offset = readULong(src, index + 4); |
|
207 if (src.length < offset + length) |
|
208 throw new NTLMEngineException( |
|
209 "NTLM authentication - buffer too small for data item"); |
|
210 byte[] buffer = new byte[length]; |
|
211 System.arraycopy(src, offset, buffer, 0, length); |
|
212 return buffer; |
|
213 } |
|
214 |
|
215 /** Calculate a challenge block */ |
|
216 private static byte[] makeRandomChallenge() throws NTLMEngineException { |
|
217 if (RND_GEN == null) { |
|
218 throw new NTLMEngineException("Random generator not available"); |
|
219 } |
|
220 byte[] rval = new byte[8]; |
|
221 synchronized (RND_GEN) { |
|
222 RND_GEN.nextBytes(rval); |
|
223 } |
|
224 return rval; |
|
225 } |
|
226 |
|
227 /** Calculate an NTLM2 challenge block */ |
|
228 private static byte[] makeNTLM2RandomChallenge() throws NTLMEngineException { |
|
229 if (RND_GEN == null) { |
|
230 throw new NTLMEngineException("Random generator not available"); |
|
231 } |
|
232 byte[] rval = new byte[24]; |
|
233 synchronized (RND_GEN) { |
|
234 RND_GEN.nextBytes(rval); |
|
235 } |
|
236 // 8-byte challenge, padded with zeros to 24 bytes. |
|
237 Arrays.fill(rval, 8, 24, (byte) 0x00); |
|
238 return rval; |
|
239 } |
|
240 |
|
241 /** |
|
242 * Calculates the LM Response for the given challenge, using the specified |
|
243 * password. |
|
244 * |
|
245 * @param password |
|
246 * The user's password. |
|
247 * @param challenge |
|
248 * The Type 2 challenge from the server. |
|
249 * |
|
250 * @return The LM Response. |
|
251 */ |
|
252 static byte[] getLMResponse(String password, byte[] challenge) |
|
253 throws NTLMEngineException { |
|
254 byte[] lmHash = lmHash(password); |
|
255 return lmResponse(lmHash, challenge); |
|
256 } |
|
257 |
|
258 /** |
|
259 * Calculates the NTLM Response for the given challenge, using the specified |
|
260 * password. |
|
261 * |
|
262 * @param password |
|
263 * The user's password. |
|
264 * @param challenge |
|
265 * The Type 2 challenge from the server. |
|
266 * |
|
267 * @return The NTLM Response. |
|
268 */ |
|
269 static byte[] getNTLMResponse(String password, byte[] challenge) |
|
270 throws NTLMEngineException { |
|
271 byte[] ntlmHash = ntlmHash(password); |
|
272 return lmResponse(ntlmHash, challenge); |
|
273 } |
|
274 |
|
275 /** |
|
276 * Calculates the NTLMv2 Response for the given challenge, using the |
|
277 * specified authentication target, username, password, target information |
|
278 * block, and client challenge. |
|
279 * |
|
280 * @param target |
|
281 * The authentication target (i.e., domain). |
|
282 * @param user |
|
283 * The username. |
|
284 * @param password |
|
285 * The user's password. |
|
286 * @param targetInformation |
|
287 * The target information block from the Type 2 message. |
|
288 * @param challenge |
|
289 * The Type 2 challenge from the server. |
|
290 * @param clientChallenge |
|
291 * The random 8-byte client challenge. |
|
292 * |
|
293 * @return The NTLMv2 Response. |
|
294 */ |
|
295 static byte[] getNTLMv2Response(String target, String user, String password, |
|
296 byte[] challenge, byte[] clientChallenge, byte[] targetInformation) |
|
297 throws NTLMEngineException { |
|
298 byte[] ntlmv2Hash = ntlmv2Hash(target, user, password); |
|
299 byte[] blob = createBlob(clientChallenge, targetInformation); |
|
300 return lmv2Response(ntlmv2Hash, challenge, blob); |
|
301 } |
|
302 |
|
303 /** |
|
304 * Calculates the LMv2 Response for the given challenge, using the specified |
|
305 * authentication target, username, password, and client challenge. |
|
306 * |
|
307 * @param target |
|
308 * The authentication target (i.e., domain). |
|
309 * @param user |
|
310 * The username. |
|
311 * @param password |
|
312 * The user's password. |
|
313 * @param challenge |
|
314 * The Type 2 challenge from the server. |
|
315 * @param clientChallenge |
|
316 * The random 8-byte client challenge. |
|
317 * |
|
318 * @return The LMv2 Response. |
|
319 */ |
|
320 static byte[] getLMv2Response(String target, String user, String password, |
|
321 byte[] challenge, byte[] clientChallenge) throws NTLMEngineException { |
|
322 byte[] ntlmv2Hash = ntlmv2Hash(target, user, password); |
|
323 return lmv2Response(ntlmv2Hash, challenge, clientChallenge); |
|
324 } |
|
325 |
|
326 /** |
|
327 * Calculates the NTLM2 Session Response for the given challenge, using the |
|
328 * specified password and client challenge. |
|
329 * |
|
330 * @param password |
|
331 * The user's password. |
|
332 * @param challenge |
|
333 * The Type 2 challenge from the server. |
|
334 * @param clientChallenge |
|
335 * The random 8-byte client challenge. |
|
336 * |
|
337 * @return The NTLM2 Session Response. This is placed in the NTLM response |
|
338 * field of the Type 3 message; the LM response field contains the |
|
339 * client challenge, null-padded to 24 bytes. |
|
340 */ |
|
341 static byte[] getNTLM2SessionResponse(String password, byte[] challenge, |
|
342 byte[] clientChallenge) throws NTLMEngineException { |
|
343 try { |
|
344 byte[] ntlmHash = ntlmHash(password); |
|
345 |
|
346 // Look up MD5 algorithm (was necessary on jdk 1.4.2) |
|
347 // This used to be needed, but java 1.5.0_07 includes the MD5 |
|
348 // algorithm (finally) |
|
349 // Class x = Class.forName("gnu.crypto.hash.MD5"); |
|
350 // Method updateMethod = x.getMethod("update",new |
|
351 // Class[]{byte[].class}); |
|
352 // Method digestMethod = x.getMethod("digest",new Class[0]); |
|
353 // Object mdInstance = x.newInstance(); |
|
354 // updateMethod.invoke(mdInstance,new Object[]{challenge}); |
|
355 // updateMethod.invoke(mdInstance,new Object[]{clientChallenge}); |
|
356 // byte[] digest = (byte[])digestMethod.invoke(mdInstance,new |
|
357 // Object[0]); |
|
358 |
|
359 MessageDigest md5 = MessageDigest.getInstance("MD5"); |
|
360 md5.update(challenge); |
|
361 md5.update(clientChallenge); |
|
362 byte[] digest = md5.digest(); |
|
363 |
|
364 byte[] sessionHash = new byte[8]; |
|
365 System.arraycopy(digest, 0, sessionHash, 0, 8); |
|
366 return lmResponse(ntlmHash, sessionHash); |
|
367 } catch (Exception e) { |
|
368 if (e instanceof NTLMEngineException) |
|
369 throw (NTLMEngineException) e; |
|
370 throw new NTLMEngineException(e.getMessage(), e); |
|
371 } |
|
372 } |
|
373 |
|
374 /** |
|
375 * Creates the LM Hash of the user's password. |
|
376 * |
|
377 * @param password |
|
378 * The password. |
|
379 * |
|
380 * @return The LM Hash of the given password, used in the calculation of the |
|
381 * LM Response. |
|
382 */ |
|
383 private static byte[] lmHash(String password) throws NTLMEngineException { |
|
384 try { |
|
385 byte[] oemPassword = password.toUpperCase().getBytes("US-ASCII"); |
|
386 int length = Math.min(oemPassword.length, 14); |
|
387 byte[] keyBytes = new byte[14]; |
|
388 System.arraycopy(oemPassword, 0, keyBytes, 0, length); |
|
389 Key lowKey = createDESKey(keyBytes, 0); |
|
390 Key highKey = createDESKey(keyBytes, 7); |
|
391 byte[] magicConstant = "KGS!@#$%".getBytes("US-ASCII"); |
|
392 Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); |
|
393 des.init(Cipher.ENCRYPT_MODE, lowKey); |
|
394 byte[] lowHash = des.doFinal(magicConstant); |
|
395 des.init(Cipher.ENCRYPT_MODE, highKey); |
|
396 byte[] highHash = des.doFinal(magicConstant); |
|
397 byte[] lmHash = new byte[16]; |
|
398 System.arraycopy(lowHash, 0, lmHash, 0, 8); |
|
399 System.arraycopy(highHash, 0, lmHash, 8, 8); |
|
400 return lmHash; |
|
401 } catch (Exception e) { |
|
402 throw new NTLMEngineException(e.getMessage(), e); |
|
403 } |
|
404 } |
|
405 |
|
406 /** |
|
407 * Creates the NTLM Hash of the user's password. |
|
408 * |
|
409 * @param password |
|
410 * The password. |
|
411 * |
|
412 * @return The NTLM Hash of the given password, used in the calculation of |
|
413 * the NTLM Response and the NTLMv2 and LMv2 Hashes. |
|
414 */ |
|
415 private static byte[] ntlmHash(String password) throws NTLMEngineException { |
|
416 try { |
|
417 byte[] unicodePassword = password.getBytes("UnicodeLittleUnmarked"); |
|
418 MD4 md4 = new MD4(); |
|
419 md4.update(unicodePassword); |
|
420 return md4.getOutput(); |
|
421 } catch (java.io.UnsupportedEncodingException e) { |
|
422 throw new NTLMEngineException("Unicode not supported: " + e.getMessage(), e); |
|
423 } |
|
424 } |
|
425 |
|
426 /** |
|
427 * Creates the NTLMv2 Hash of the user's password. |
|
428 * |
|
429 * @param target |
|
430 * The authentication target (i.e., domain). |
|
431 * @param user |
|
432 * The username. |
|
433 * @param password |
|
434 * The password. |
|
435 * |
|
436 * @return The NTLMv2 Hash, used in the calculation of the NTLMv2 and LMv2 |
|
437 * Responses. |
|
438 */ |
|
439 private static byte[] ntlmv2Hash(String target, String user, String password) |
|
440 throws NTLMEngineException { |
|
441 try { |
|
442 byte[] ntlmHash = ntlmHash(password); |
|
443 HMACMD5 hmacMD5 = new HMACMD5(ntlmHash); |
|
444 // Upper case username, mixed case target!! |
|
445 hmacMD5.update(user.toUpperCase().getBytes("UnicodeLittleUnmarked")); |
|
446 hmacMD5.update(target.getBytes("UnicodeLittleUnmarked")); |
|
447 return hmacMD5.getOutput(); |
|
448 } catch (java.io.UnsupportedEncodingException e) { |
|
449 throw new NTLMEngineException("Unicode not supported! " + e.getMessage(), e); |
|
450 } |
|
451 } |
|
452 |
|
453 /** |
|
454 * Creates the LM Response from the given hash and Type 2 challenge. |
|
455 * |
|
456 * @param hash |
|
457 * The LM or NTLM Hash. |
|
458 * @param challenge |
|
459 * The server challenge from the Type 2 message. |
|
460 * |
|
461 * @return The response (either LM or NTLM, depending on the provided hash). |
|
462 */ |
|
463 private static byte[] lmResponse(byte[] hash, byte[] challenge) throws NTLMEngineException { |
|
464 try { |
|
465 byte[] keyBytes = new byte[21]; |
|
466 System.arraycopy(hash, 0, keyBytes, 0, 16); |
|
467 Key lowKey = createDESKey(keyBytes, 0); |
|
468 Key middleKey = createDESKey(keyBytes, 7); |
|
469 Key highKey = createDESKey(keyBytes, 14); |
|
470 Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); |
|
471 des.init(Cipher.ENCRYPT_MODE, lowKey); |
|
472 byte[] lowResponse = des.doFinal(challenge); |
|
473 des.init(Cipher.ENCRYPT_MODE, middleKey); |
|
474 byte[] middleResponse = des.doFinal(challenge); |
|
475 des.init(Cipher.ENCRYPT_MODE, highKey); |
|
476 byte[] highResponse = des.doFinal(challenge); |
|
477 byte[] lmResponse = new byte[24]; |
|
478 System.arraycopy(lowResponse, 0, lmResponse, 0, 8); |
|
479 System.arraycopy(middleResponse, 0, lmResponse, 8, 8); |
|
480 System.arraycopy(highResponse, 0, lmResponse, 16, 8); |
|
481 return lmResponse; |
|
482 } catch (Exception e) { |
|
483 throw new NTLMEngineException(e.getMessage(), e); |
|
484 } |
|
485 } |
|
486 |
|
487 /** |
|
488 * Creates the LMv2 Response from the given hash, client data, and Type 2 |
|
489 * challenge. |
|
490 * |
|
491 * @param hash |
|
492 * The NTLMv2 Hash. |
|
493 * @param clientData |
|
494 * The client data (blob or client challenge). |
|
495 * @param challenge |
|
496 * The server challenge from the Type 2 message. |
|
497 * |
|
498 * @return The response (either NTLMv2 or LMv2, depending on the client |
|
499 * data). |
|
500 */ |
|
501 private static byte[] lmv2Response(byte[] hash, byte[] challenge, byte[] clientData) |
|
502 throws NTLMEngineException { |
|
503 HMACMD5 hmacMD5 = new HMACMD5(hash); |
|
504 hmacMD5.update(challenge); |
|
505 hmacMD5.update(clientData); |
|
506 byte[] mac = hmacMD5.getOutput(); |
|
507 byte[] lmv2Response = new byte[mac.length + clientData.length]; |
|
508 System.arraycopy(mac, 0, lmv2Response, 0, mac.length); |
|
509 System.arraycopy(clientData, 0, lmv2Response, mac.length, clientData.length); |
|
510 return lmv2Response; |
|
511 } |
|
512 |
|
513 /** |
|
514 * Creates the NTLMv2 blob from the given target information block and |
|
515 * client challenge. |
|
516 * |
|
517 * @param targetInformation |
|
518 * The target information block from the Type 2 message. |
|
519 * @param clientChallenge |
|
520 * The random 8-byte client challenge. |
|
521 * |
|
522 * @return The blob, used in the calculation of the NTLMv2 Response. |
|
523 */ |
|
524 private static byte[] createBlob(byte[] clientChallenge, byte[] targetInformation) { |
|
525 byte[] blobSignature = new byte[] { (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00 }; |
|
526 byte[] reserved = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; |
|
527 byte[] unknown1 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; |
|
528 long time = System.currentTimeMillis(); |
|
529 time += 11644473600000l; // milliseconds from January 1, 1601 -> epoch. |
|
530 time *= 10000; // tenths of a microsecond. |
|
531 // convert to little-endian byte array. |
|
532 byte[] timestamp = new byte[8]; |
|
533 for (int i = 0; i < 8; i++) { |
|
534 timestamp[i] = (byte) time; |
|
535 time >>>= 8; |
|
536 } |
|
537 byte[] blob = new byte[blobSignature.length + reserved.length + timestamp.length + 8 |
|
538 + unknown1.length + targetInformation.length]; |
|
539 int offset = 0; |
|
540 System.arraycopy(blobSignature, 0, blob, offset, blobSignature.length); |
|
541 offset += blobSignature.length; |
|
542 System.arraycopy(reserved, 0, blob, offset, reserved.length); |
|
543 offset += reserved.length; |
|
544 System.arraycopy(timestamp, 0, blob, offset, timestamp.length); |
|
545 offset += timestamp.length; |
|
546 System.arraycopy(clientChallenge, 0, blob, offset, 8); |
|
547 offset += 8; |
|
548 System.arraycopy(unknown1, 0, blob, offset, unknown1.length); |
|
549 offset += unknown1.length; |
|
550 System.arraycopy(targetInformation, 0, blob, offset, targetInformation.length); |
|
551 return blob; |
|
552 } |
|
553 |
|
554 /** |
|
555 * Creates a DES encryption key from the given key material. |
|
556 * |
|
557 * @param bytes |
|
558 * A byte array containing the DES key material. |
|
559 * @param offset |
|
560 * The offset in the given byte array at which the 7-byte key |
|
561 * material starts. |
|
562 * |
|
563 * @return A DES encryption key created from the key material starting at |
|
564 * the specified offset in the given byte array. |
|
565 */ |
|
566 private static Key createDESKey(byte[] bytes, int offset) { |
|
567 byte[] keyBytes = new byte[7]; |
|
568 System.arraycopy(bytes, offset, keyBytes, 0, 7); |
|
569 byte[] material = new byte[8]; |
|
570 material[0] = keyBytes[0]; |
|
571 material[1] = (byte) (keyBytes[0] << 7 | (keyBytes[1] & 0xff) >>> 1); |
|
572 material[2] = (byte) (keyBytes[1] << 6 | (keyBytes[2] & 0xff) >>> 2); |
|
573 material[3] = (byte) (keyBytes[2] << 5 | (keyBytes[3] & 0xff) >>> 3); |
|
574 material[4] = (byte) (keyBytes[3] << 4 | (keyBytes[4] & 0xff) >>> 4); |
|
575 material[5] = (byte) (keyBytes[4] << 3 | (keyBytes[5] & 0xff) >>> 5); |
|
576 material[6] = (byte) (keyBytes[5] << 2 | (keyBytes[6] & 0xff) >>> 6); |
|
577 material[7] = (byte) (keyBytes[6] << 1); |
|
578 oddParity(material); |
|
579 return new SecretKeySpec(material, "DES"); |
|
580 } |
|
581 |
|
582 /** |
|
583 * Applies odd parity to the given byte array. |
|
584 * |
|
585 * @param bytes |
|
586 * The data whose parity bits are to be adjusted for odd parity. |
|
587 */ |
|
588 private static void oddParity(byte[] bytes) { |
|
589 for (int i = 0; i < bytes.length; i++) { |
|
590 byte b = bytes[i]; |
|
591 boolean needsParity = (((b >>> 7) ^ (b >>> 6) ^ (b >>> 5) ^ (b >>> 4) ^ (b >>> 3) |
|
592 ^ (b >>> 2) ^ (b >>> 1)) & 0x01) == 0; |
|
593 if (needsParity) { |
|
594 bytes[i] |= (byte) 0x01; |
|
595 } else { |
|
596 bytes[i] &= (byte) 0xfe; |
|
597 } |
|
598 } |
|
599 } |
|
600 |
|
601 /** NTLM message generation, base class */ |
|
602 static class NTLMMessage { |
|
603 /** The current response */ |
|
604 private byte[] messageContents = null; |
|
605 |
|
606 /** The current output position */ |
|
607 private int currentOutputPosition = 0; |
|
608 |
|
609 /** Constructor to use when message contents are not yet known */ |
|
610 NTLMMessage() { |
|
611 } |
|
612 |
|
613 /** Constructor to use when message contents are known */ |
|
614 NTLMMessage(String messageBody, int expectedType) throws NTLMEngineException { |
|
615 messageContents = Base64.decodeBase64(EncodingUtils.getBytes(messageBody, |
|
616 DEFAULT_CHARSET)); |
|
617 // Look for NTLM message |
|
618 if (messageContents.length < SIGNATURE.length) |
|
619 throw new NTLMEngineException("NTLM message decoding error - packet too short"); |
|
620 int i = 0; |
|
621 while (i < SIGNATURE.length) { |
|
622 if (messageContents[i] != SIGNATURE[i]) |
|
623 throw new NTLMEngineException( |
|
624 "NTLM message expected - instead got unrecognized bytes"); |
|
625 i++; |
|
626 } |
|
627 |
|
628 // Check to be sure there's a type 2 message indicator next |
|
629 int type = readULong(SIGNATURE.length); |
|
630 if (type != expectedType) |
|
631 throw new NTLMEngineException("NTLM type " + Integer.toString(expectedType) |
|
632 + " message expected - instead got type " + Integer.toString(type)); |
|
633 |
|
634 currentOutputPosition = messageContents.length; |
|
635 } |
|
636 |
|
637 /** |
|
638 * Get the length of the signature and flags, so calculations can adjust |
|
639 * offsets accordingly. |
|
640 */ |
|
641 protected int getPreambleLength() { |
|
642 return SIGNATURE.length + 4; |
|
643 } |
|
644 |
|
645 /** Get the message length */ |
|
646 protected int getMessageLength() { |
|
647 return currentOutputPosition; |
|
648 } |
|
649 |
|
650 /** Read a byte from a position within the message buffer */ |
|
651 protected byte readByte(int position) throws NTLMEngineException { |
|
652 if (messageContents.length < position + 1) |
|
653 throw new NTLMEngineException("NTLM: Message too short"); |
|
654 return messageContents[position]; |
|
655 } |
|
656 |
|
657 /** Read a bunch of bytes from a position in the message buffer */ |
|
658 protected void readBytes(byte[] buffer, int position) throws NTLMEngineException { |
|
659 if (messageContents.length < position + buffer.length) |
|
660 throw new NTLMEngineException("NTLM: Message too short"); |
|
661 System.arraycopy(messageContents, position, buffer, 0, buffer.length); |
|
662 } |
|
663 |
|
664 /** Read a ushort from a position within the message buffer */ |
|
665 protected int readUShort(int position) throws NTLMEngineException { |
|
666 return NTLMEngineImpl.readUShort(messageContents, position); |
|
667 } |
|
668 |
|
669 /** Read a ulong from a position within the message buffer */ |
|
670 protected int readULong(int position) throws NTLMEngineException { |
|
671 return NTLMEngineImpl.readULong(messageContents, position); |
|
672 } |
|
673 |
|
674 /** Read a security buffer from a position within the message buffer */ |
|
675 protected byte[] readSecurityBuffer(int position) throws NTLMEngineException { |
|
676 return NTLMEngineImpl.readSecurityBuffer(messageContents, position); |
|
677 } |
|
678 |
|
679 /** |
|
680 * Prepares the object to create a response of the given length. |
|
681 * |
|
682 * @param length |
|
683 * the maximum length of the response to prepare, not |
|
684 * including the type and the signature (which this method |
|
685 * adds). |
|
686 */ |
|
687 protected void prepareResponse(int maxlength, int messageType) { |
|
688 messageContents = new byte[maxlength]; |
|
689 currentOutputPosition = 0; |
|
690 addBytes(SIGNATURE); |
|
691 addULong(messageType); |
|
692 } |
|
693 |
|
694 /** |
|
695 * Adds the given byte to the response. |
|
696 * |
|
697 * @param b |
|
698 * the byte to add. |
|
699 */ |
|
700 protected void addByte(byte b) { |
|
701 messageContents[currentOutputPosition] = b; |
|
702 currentOutputPosition++; |
|
703 } |
|
704 |
|
705 /** |
|
706 * Adds the given bytes to the response. |
|
707 * |
|
708 * @param bytes |
|
709 * the bytes to add. |
|
710 */ |
|
711 protected void addBytes(byte[] bytes) { |
|
712 for (int i = 0; i < bytes.length; i++) { |
|
713 messageContents[currentOutputPosition] = bytes[i]; |
|
714 currentOutputPosition++; |
|
715 } |
|
716 } |
|
717 |
|
718 /** Adds a USHORT to the response */ |
|
719 protected void addUShort(int value) { |
|
720 addByte((byte) (value & 0xff)); |
|
721 addByte((byte) (value >> 8 & 0xff)); |
|
722 } |
|
723 |
|
724 /** Adds a ULong to the response */ |
|
725 protected void addULong(int value) { |
|
726 addByte((byte) (value & 0xff)); |
|
727 addByte((byte) (value >> 8 & 0xff)); |
|
728 addByte((byte) (value >> 16 & 0xff)); |
|
729 addByte((byte) (value >> 24 & 0xff)); |
|
730 } |
|
731 |
|
732 /** |
|
733 * Returns the response that has been generated after shrinking the |
|
734 * array if required and base64 encodes the response. |
|
735 * |
|
736 * @return The response as above. |
|
737 */ |
|
738 String getResponse() { |
|
739 byte[] resp; |
|
740 if (messageContents.length > currentOutputPosition) { |
|
741 byte[] tmp = new byte[currentOutputPosition]; |
|
742 for (int i = 0; i < currentOutputPosition; i++) { |
|
743 tmp[i] = messageContents[i]; |
|
744 } |
|
745 resp = tmp; |
|
746 } else { |
|
747 resp = messageContents; |
|
748 } |
|
749 return EncodingUtils.getAsciiString(Base64.encodeBase64(resp)); |
|
750 } |
|
751 |
|
752 } |
|
753 |
|
754 /** Type 1 message assembly class */ |
|
755 static class Type1Message extends NTLMMessage { |
|
756 protected byte[] hostBytes; |
|
757 protected byte[] domainBytes; |
|
758 |
|
759 /** Constructor. Include the arguments the message will need */ |
|
760 Type1Message(String domain, String host) throws NTLMEngineException { |
|
761 super(); |
|
762 try { |
|
763 // Strip off domain name from the host! |
|
764 host = convertHost(host); |
|
765 // Use only the base domain name! |
|
766 domain = convertDomain(domain); |
|
767 |
|
768 hostBytes = host.getBytes("UnicodeLittleUnmarked"); |
|
769 domainBytes = domain.toUpperCase().getBytes("UnicodeLittleUnmarked"); |
|
770 } catch (java.io.UnsupportedEncodingException e) { |
|
771 throw new NTLMEngineException("Unicode unsupported: " + e.getMessage(), e); |
|
772 } |
|
773 } |
|
774 |
|
775 /** |
|
776 * Getting the response involves building the message before returning |
|
777 * it |
|
778 */ |
|
779 @Override |
|
780 String getResponse() { |
|
781 // Now, build the message. Calculate its length first, including |
|
782 // signature or type. |
|
783 int finalLength = 32 + hostBytes.length + domainBytes.length; |
|
784 |
|
785 // Set up the response. This will initialize the signature, message |
|
786 // type, and flags. |
|
787 prepareResponse(finalLength, 1); |
|
788 |
|
789 // Flags. These are the complete set of flags we support. |
|
790 addULong(FLAG_NEGOTIATE_NTLM | FLAG_NEGOTIATE_NTLM2 | FLAG_NEGOTIATE_SIGN |
|
791 | FLAG_NEGOTIATE_SEAL | |
|
792 /* |
|
793 * FLAG_NEGOTIATE_ALWAYS_SIGN | FLAG_NEGOTIATE_KEY_EXCH | |
|
794 */ |
|
795 FLAG_UNICODE_ENCODING | FLAG_TARGET_DESIRED | FLAG_NEGOTIATE_128); |
|
796 |
|
797 // Domain length (two times). |
|
798 addUShort(domainBytes.length); |
|
799 addUShort(domainBytes.length); |
|
800 |
|
801 // Domain offset. |
|
802 addULong(hostBytes.length + 32); |
|
803 |
|
804 // Host length (two times). |
|
805 addUShort(hostBytes.length); |
|
806 addUShort(hostBytes.length); |
|
807 |
|
808 // Host offset (always 32). |
|
809 addULong(32); |
|
810 |
|
811 // Host String. |
|
812 addBytes(hostBytes); |
|
813 |
|
814 // Domain String. |
|
815 addBytes(domainBytes); |
|
816 |
|
817 return super.getResponse(); |
|
818 } |
|
819 |
|
820 } |
|
821 |
|
822 /** Type 2 message class */ |
|
823 static class Type2Message extends NTLMMessage { |
|
824 protected byte[] challenge; |
|
825 protected String target; |
|
826 protected byte[] targetInfo; |
|
827 protected int flags; |
|
828 |
|
829 Type2Message(String message) throws NTLMEngineException { |
|
830 super(message, 2); |
|
831 |
|
832 // Parse out the rest of the info we need from the message |
|
833 // The nonce is the 8 bytes starting from the byte in position 24. |
|
834 challenge = new byte[8]; |
|
835 readBytes(challenge, 24); |
|
836 |
|
837 flags = readULong(20); |
|
838 if ((flags & FLAG_UNICODE_ENCODING) == 0) |
|
839 throw new NTLMEngineException( |
|
840 "NTLM type 2 message has flags that make no sense: " |
|
841 + Integer.toString(flags)); |
|
842 // Do the target! |
|
843 target = null; |
|
844 // The TARGET_DESIRED flag is said to not have understood semantics |
|
845 // in Type2 messages, so use the length of the packet to decide |
|
846 // how to proceed instead |
|
847 if (getMessageLength() >= 12 + 8) { |
|
848 byte[] bytes = readSecurityBuffer(12); |
|
849 if (bytes.length != 0) { |
|
850 try { |
|
851 target = new String(bytes, "UnicodeLittleUnmarked"); |
|
852 } catch (java.io.UnsupportedEncodingException e) { |
|
853 throw new NTLMEngineException(e.getMessage(), e); |
|
854 } |
|
855 } |
|
856 } |
|
857 |
|
858 // Do the target info! |
|
859 targetInfo = null; |
|
860 // TARGET_DESIRED flag cannot be relied on, so use packet length |
|
861 if (getMessageLength() >= 40 + 8) { |
|
862 byte[] bytes = readSecurityBuffer(40); |
|
863 if (bytes.length != 0) { |
|
864 targetInfo = bytes; |
|
865 } |
|
866 } |
|
867 } |
|
868 |
|
869 /** Retrieve the challenge */ |
|
870 byte[] getChallenge() { |
|
871 return challenge; |
|
872 } |
|
873 |
|
874 /** Retrieve the target */ |
|
875 String getTarget() { |
|
876 return target; |
|
877 } |
|
878 |
|
879 /** Retrieve the target info */ |
|
880 byte[] getTargetInfo() { |
|
881 return targetInfo; |
|
882 } |
|
883 |
|
884 /** Retrieve the response flags */ |
|
885 int getFlags() { |
|
886 return flags; |
|
887 } |
|
888 |
|
889 } |
|
890 |
|
891 /** Type 3 message assembly class */ |
|
892 static class Type3Message extends NTLMMessage { |
|
893 // Response flags from the type2 message |
|
894 protected int type2Flags; |
|
895 |
|
896 protected byte[] domainBytes; |
|
897 protected byte[] hostBytes; |
|
898 protected byte[] userBytes; |
|
899 |
|
900 protected byte[] lmResp; |
|
901 protected byte[] ntResp; |
|
902 |
|
903 /** Constructor. Pass the arguments we will need */ |
|
904 Type3Message(String domain, String host, String user, String password, byte[] nonce, |
|
905 int type2Flags, String target, byte[] targetInformation) |
|
906 throws NTLMEngineException { |
|
907 // Save the flags |
|
908 this.type2Flags = type2Flags; |
|
909 |
|
910 // Strip off domain name from the host! |
|
911 host = convertHost(host); |
|
912 // Use only the base domain name! |
|
913 domain = convertDomain(domain); |
|
914 |
|
915 // Use the new code to calculate the responses, including v2 if that |
|
916 // seems warranted. |
|
917 try { |
|
918 if (targetInformation != null && target != null) { |
|
919 byte[] clientChallenge = makeRandomChallenge(); |
|
920 ntResp = getNTLMv2Response(target, user, password, nonce, clientChallenge, |
|
921 targetInformation); |
|
922 lmResp = getLMv2Response(target, user, password, nonce, clientChallenge); |
|
923 } else { |
|
924 if ((type2Flags & FLAG_NEGOTIATE_NTLM2) != 0) { |
|
925 // NTLM2 session stuff is requested |
|
926 byte[] clientChallenge = makeNTLM2RandomChallenge(); |
|
927 |
|
928 ntResp = getNTLM2SessionResponse(password, nonce, clientChallenge); |
|
929 lmResp = clientChallenge; |
|
930 |
|
931 // All the other flags we send (signing, sealing, key |
|
932 // exchange) are supported, but they don't do anything |
|
933 // at all in an |
|
934 // NTLM2 context! So we're done at this point. |
|
935 } else { |
|
936 ntResp = getNTLMResponse(password, nonce); |
|
937 lmResp = getLMResponse(password, nonce); |
|
938 } |
|
939 } |
|
940 } catch (NTLMEngineException e) { |
|
941 // This likely means we couldn't find the MD4 hash algorithm - |
|
942 // fail back to just using LM |
|
943 ntResp = new byte[0]; |
|
944 lmResp = getLMResponse(password, nonce); |
|
945 } |
|
946 |
|
947 try { |
|
948 domainBytes = domain.toUpperCase().getBytes("UnicodeLittleUnmarked"); |
|
949 hostBytes = host.getBytes("UnicodeLittleUnmarked"); |
|
950 userBytes = user.getBytes("UnicodeLittleUnmarked"); |
|
951 } catch (java.io.UnsupportedEncodingException e) { |
|
952 throw new NTLMEngineException("Unicode not supported: " + e.getMessage(), e); |
|
953 } |
|
954 } |
|
955 |
|
956 /** Assemble the response */ |
|
957 @Override |
|
958 String getResponse() { |
|
959 int ntRespLen = ntResp.length; |
|
960 int lmRespLen = lmResp.length; |
|
961 |
|
962 int domainLen = domainBytes.length; |
|
963 int hostLen = hostBytes.length; |
|
964 int userLen = userBytes.length; |
|
965 |
|
966 // Calculate the layout within the packet |
|
967 int lmRespOffset = 64; |
|
968 int ntRespOffset = lmRespOffset + lmRespLen; |
|
969 int domainOffset = ntRespOffset + ntRespLen; |
|
970 int userOffset = domainOffset + domainLen; |
|
971 int hostOffset = userOffset + userLen; |
|
972 int sessionKeyOffset = hostOffset + hostLen; |
|
973 int finalLength = sessionKeyOffset + 0; |
|
974 |
|
975 // Start the response. Length includes signature and type |
|
976 prepareResponse(finalLength, 3); |
|
977 |
|
978 // LM Resp Length (twice) |
|
979 addUShort(lmRespLen); |
|
980 addUShort(lmRespLen); |
|
981 |
|
982 // LM Resp Offset |
|
983 addULong(lmRespOffset); |
|
984 |
|
985 // NT Resp Length (twice) |
|
986 addUShort(ntRespLen); |
|
987 addUShort(ntRespLen); |
|
988 |
|
989 // NT Resp Offset |
|
990 addULong(ntRespOffset); |
|
991 |
|
992 // Domain length (twice) |
|
993 addUShort(domainLen); |
|
994 addUShort(domainLen); |
|
995 |
|
996 // Domain offset. |
|
997 addULong(domainOffset); |
|
998 |
|
999 // User Length (twice) |
|
1000 addUShort(userLen); |
|
1001 addUShort(userLen); |
|
1002 |
|
1003 // User offset |
|
1004 addULong(userOffset); |
|
1005 |
|
1006 // Host length (twice) |
|
1007 addUShort(hostLen); |
|
1008 addUShort(hostLen); |
|
1009 |
|
1010 // Host offset |
|
1011 addULong(hostOffset); |
|
1012 |
|
1013 // 4 bytes of zeros - not sure what this is |
|
1014 addULong(0); |
|
1015 |
|
1016 // Message length |
|
1017 addULong(finalLength); |
|
1018 |
|
1019 // Flags. Currently: NEGOTIATE_NTLM + UNICODE_ENCODING + |
|
1020 // TARGET_DESIRED + NEGOTIATE_128 |
|
1021 addULong(FLAG_NEGOTIATE_NTLM | FLAG_UNICODE_ENCODING | FLAG_TARGET_DESIRED |
|
1022 | FLAG_NEGOTIATE_128 | (type2Flags & FLAG_NEGOTIATE_NTLM2) |
|
1023 | (type2Flags & FLAG_NEGOTIATE_SIGN) | (type2Flags & FLAG_NEGOTIATE_SEAL) |
|
1024 | (type2Flags & FLAG_NEGOTIATE_KEY_EXCH) |
|
1025 | (type2Flags & FLAG_NEGOTIATE_ALWAYS_SIGN)); |
|
1026 |
|
1027 // Add the actual data |
|
1028 addBytes(lmResp); |
|
1029 addBytes(ntResp); |
|
1030 addBytes(domainBytes); |
|
1031 addBytes(userBytes); |
|
1032 addBytes(hostBytes); |
|
1033 |
|
1034 return super.getResponse(); |
|
1035 } |
|
1036 } |
|
1037 |
|
1038 static void writeULong(byte[] buffer, int value, int offset) { |
|
1039 buffer[offset] = (byte) (value & 0xff); |
|
1040 buffer[offset + 1] = (byte) (value >> 8 & 0xff); |
|
1041 buffer[offset + 2] = (byte) (value >> 16 & 0xff); |
|
1042 buffer[offset + 3] = (byte) (value >> 24 & 0xff); |
|
1043 } |
|
1044 |
|
1045 static int F(int x, int y, int z) { |
|
1046 return ((x & y) | (~x & z)); |
|
1047 } |
|
1048 |
|
1049 static int G(int x, int y, int z) { |
|
1050 return ((x & y) | (x & z) | (y & z)); |
|
1051 } |
|
1052 |
|
1053 static int H(int x, int y, int z) { |
|
1054 return (x ^ y ^ z); |
|
1055 } |
|
1056 |
|
1057 static int rotintlft(int val, int numbits) { |
|
1058 return ((val << numbits) | (val >>> (32 - numbits))); |
|
1059 } |
|
1060 |
|
1061 /** |
|
1062 * Cryptography support - MD4. The following class was based loosely on the |
|
1063 * RFC and on code found at http://www.cs.umd.edu/~harry/jotp/src/md.java. |
|
1064 * Code correctness was verified by looking at MD4.java from the jcifs |
|
1065 * library (http://jcifs.samba.org). It was massaged extensively to the |
|
1066 * final form found here by Karl Wright (kwright@metacarta.com). |
|
1067 */ |
|
1068 static class MD4 { |
|
1069 protected int A = 0x67452301; |
|
1070 protected int B = 0xefcdab89; |
|
1071 protected int C = 0x98badcfe; |
|
1072 protected int D = 0x10325476; |
|
1073 protected long count = 0L; |
|
1074 protected byte[] dataBuffer = new byte[64]; |
|
1075 |
|
1076 MD4() { |
|
1077 } |
|
1078 |
|
1079 void update(byte[] input) { |
|
1080 // We always deal with 512 bits at a time. Correspondingly, there is |
|
1081 // a buffer 64 bytes long that we write data into until it gets |
|
1082 // full. |
|
1083 int curBufferPos = (int) (count & 63L); |
|
1084 int inputIndex = 0; |
|
1085 while (input.length - inputIndex + curBufferPos >= dataBuffer.length) { |
|
1086 // We have enough data to do the next step. Do a partial copy |
|
1087 // and a transform, updating inputIndex and curBufferPos |
|
1088 // accordingly |
|
1089 int transferAmt = dataBuffer.length - curBufferPos; |
|
1090 System.arraycopy(input, inputIndex, dataBuffer, curBufferPos, transferAmt); |
|
1091 count += transferAmt; |
|
1092 curBufferPos = 0; |
|
1093 inputIndex += transferAmt; |
|
1094 processBuffer(); |
|
1095 } |
|
1096 |
|
1097 // If there's anything left, copy it into the buffer and leave it. |
|
1098 // We know there's not enough left to process. |
|
1099 if (inputIndex < input.length) { |
|
1100 int transferAmt = input.length - inputIndex; |
|
1101 System.arraycopy(input, inputIndex, dataBuffer, curBufferPos, transferAmt); |
|
1102 count += transferAmt; |
|
1103 curBufferPos += transferAmt; |
|
1104 } |
|
1105 } |
|
1106 |
|
1107 byte[] getOutput() { |
|
1108 // Feed pad/length data into engine. This must round out the input |
|
1109 // to a multiple of 512 bits. |
|
1110 int bufferIndex = (int) (count & 63L); |
|
1111 int padLen = (bufferIndex < 56) ? (56 - bufferIndex) : (120 - bufferIndex); |
|
1112 byte[] postBytes = new byte[padLen + 8]; |
|
1113 // Leading 0x80, specified amount of zero padding, then length in |
|
1114 // bits. |
|
1115 postBytes[0] = (byte) 0x80; |
|
1116 // Fill out the last 8 bytes with the length |
|
1117 for (int i = 0; i < 8; i++) { |
|
1118 postBytes[padLen + i] = (byte) ((count * 8) >>> (8 * i)); |
|
1119 } |
|
1120 |
|
1121 // Update the engine |
|
1122 update(postBytes); |
|
1123 |
|
1124 // Calculate final result |
|
1125 byte[] result = new byte[16]; |
|
1126 writeULong(result, A, 0); |
|
1127 writeULong(result, B, 4); |
|
1128 writeULong(result, C, 8); |
|
1129 writeULong(result, D, 12); |
|
1130 return result; |
|
1131 } |
|
1132 |
|
1133 protected void processBuffer() { |
|
1134 // Convert current buffer to 16 ulongs |
|
1135 int[] d = new int[16]; |
|
1136 |
|
1137 for (int i = 0; i < 16; i++) { |
|
1138 d[i] = (dataBuffer[i * 4] & 0xff) + ((dataBuffer[i * 4 + 1] & 0xff) << 8) |
|
1139 + ((dataBuffer[i * 4 + 2] & 0xff) << 16) |
|
1140 + ((dataBuffer[i * 4 + 3] & 0xff) << 24); |
|
1141 } |
|
1142 |
|
1143 // Do a round of processing |
|
1144 int AA = A; |
|
1145 int BB = B; |
|
1146 int CC = C; |
|
1147 int DD = D; |
|
1148 round1(d); |
|
1149 round2(d); |
|
1150 round3(d); |
|
1151 A += AA; |
|
1152 B += BB; |
|
1153 C += CC; |
|
1154 D += DD; |
|
1155 |
|
1156 } |
|
1157 |
|
1158 protected void round1(int[] d) { |
|
1159 A = rotintlft((A + F(B, C, D) + d[0]), 3); |
|
1160 D = rotintlft((D + F(A, B, C) + d[1]), 7); |
|
1161 C = rotintlft((C + F(D, A, B) + d[2]), 11); |
|
1162 B = rotintlft((B + F(C, D, A) + d[3]), 19); |
|
1163 |
|
1164 A = rotintlft((A + F(B, C, D) + d[4]), 3); |
|
1165 D = rotintlft((D + F(A, B, C) + d[5]), 7); |
|
1166 C = rotintlft((C + F(D, A, B) + d[6]), 11); |
|
1167 B = rotintlft((B + F(C, D, A) + d[7]), 19); |
|
1168 |
|
1169 A = rotintlft((A + F(B, C, D) + d[8]), 3); |
|
1170 D = rotintlft((D + F(A, B, C) + d[9]), 7); |
|
1171 C = rotintlft((C + F(D, A, B) + d[10]), 11); |
|
1172 B = rotintlft((B + F(C, D, A) + d[11]), 19); |
|
1173 |
|
1174 A = rotintlft((A + F(B, C, D) + d[12]), 3); |
|
1175 D = rotintlft((D + F(A, B, C) + d[13]), 7); |
|
1176 C = rotintlft((C + F(D, A, B) + d[14]), 11); |
|
1177 B = rotintlft((B + F(C, D, A) + d[15]), 19); |
|
1178 } |
|
1179 |
|
1180 protected void round2(int[] d) { |
|
1181 A = rotintlft((A + G(B, C, D) + d[0] + 0x5a827999), 3); |
|
1182 D = rotintlft((D + G(A, B, C) + d[4] + 0x5a827999), 5); |
|
1183 C = rotintlft((C + G(D, A, B) + d[8] + 0x5a827999), 9); |
|
1184 B = rotintlft((B + G(C, D, A) + d[12] + 0x5a827999), 13); |
|
1185 |
|
1186 A = rotintlft((A + G(B, C, D) + d[1] + 0x5a827999), 3); |
|
1187 D = rotintlft((D + G(A, B, C) + d[5] + 0x5a827999), 5); |
|
1188 C = rotintlft((C + G(D, A, B) + d[9] + 0x5a827999), 9); |
|
1189 B = rotintlft((B + G(C, D, A) + d[13] + 0x5a827999), 13); |
|
1190 |
|
1191 A = rotintlft((A + G(B, C, D) + d[2] + 0x5a827999), 3); |
|
1192 D = rotintlft((D + G(A, B, C) + d[6] + 0x5a827999), 5); |
|
1193 C = rotintlft((C + G(D, A, B) + d[10] + 0x5a827999), 9); |
|
1194 B = rotintlft((B + G(C, D, A) + d[14] + 0x5a827999), 13); |
|
1195 |
|
1196 A = rotintlft((A + G(B, C, D) + d[3] + 0x5a827999), 3); |
|
1197 D = rotintlft((D + G(A, B, C) + d[7] + 0x5a827999), 5); |
|
1198 C = rotintlft((C + G(D, A, B) + d[11] + 0x5a827999), 9); |
|
1199 B = rotintlft((B + G(C, D, A) + d[15] + 0x5a827999), 13); |
|
1200 |
|
1201 } |
|
1202 |
|
1203 protected void round3(int[] d) { |
|
1204 A = rotintlft((A + H(B, C, D) + d[0] + 0x6ed9eba1), 3); |
|
1205 D = rotintlft((D + H(A, B, C) + d[8] + 0x6ed9eba1), 9); |
|
1206 C = rotintlft((C + H(D, A, B) + d[4] + 0x6ed9eba1), 11); |
|
1207 B = rotintlft((B + H(C, D, A) + d[12] + 0x6ed9eba1), 15); |
|
1208 |
|
1209 A = rotintlft((A + H(B, C, D) + d[2] + 0x6ed9eba1), 3); |
|
1210 D = rotintlft((D + H(A, B, C) + d[10] + 0x6ed9eba1), 9); |
|
1211 C = rotintlft((C + H(D, A, B) + d[6] + 0x6ed9eba1), 11); |
|
1212 B = rotintlft((B + H(C, D, A) + d[14] + 0x6ed9eba1), 15); |
|
1213 |
|
1214 A = rotintlft((A + H(B, C, D) + d[1] + 0x6ed9eba1), 3); |
|
1215 D = rotintlft((D + H(A, B, C) + d[9] + 0x6ed9eba1), 9); |
|
1216 C = rotintlft((C + H(D, A, B) + d[5] + 0x6ed9eba1), 11); |
|
1217 B = rotintlft((B + H(C, D, A) + d[13] + 0x6ed9eba1), 15); |
|
1218 |
|
1219 A = rotintlft((A + H(B, C, D) + d[3] + 0x6ed9eba1), 3); |
|
1220 D = rotintlft((D + H(A, B, C) + d[11] + 0x6ed9eba1), 9); |
|
1221 C = rotintlft((C + H(D, A, B) + d[7] + 0x6ed9eba1), 11); |
|
1222 B = rotintlft((B + H(C, D, A) + d[15] + 0x6ed9eba1), 15); |
|
1223 |
|
1224 } |
|
1225 |
|
1226 } |
|
1227 |
|
1228 /** |
|
1229 * Cryptography support - HMACMD5 - algorithmically based on various web |
|
1230 * resources by Karl Wright |
|
1231 */ |
|
1232 static class HMACMD5 { |
|
1233 protected byte[] ipad; |
|
1234 protected byte[] opad; |
|
1235 protected MessageDigest md5; |
|
1236 |
|
1237 HMACMD5(byte[] key) throws NTLMEngineException { |
|
1238 try { |
|
1239 md5 = MessageDigest.getInstance("MD5"); |
|
1240 } catch (Exception ex) { |
|
1241 // Umm, the algorithm doesn't exist - throw an |
|
1242 // NTLMEngineException! |
|
1243 throw new NTLMEngineException( |
|
1244 "Error getting md5 message digest implementation: " + ex.getMessage(), ex); |
|
1245 } |
|
1246 |
|
1247 // Initialize the pad buffers with the key |
|
1248 ipad = new byte[64]; |
|
1249 opad = new byte[64]; |
|
1250 |
|
1251 int keyLength = key.length; |
|
1252 if (keyLength > 64) { |
|
1253 // Use MD5 of the key instead, as described in RFC 2104 |
|
1254 md5.update(key); |
|
1255 key = md5.digest(); |
|
1256 keyLength = key.length; |
|
1257 } |
|
1258 int i = 0; |
|
1259 while (i < keyLength) { |
|
1260 ipad[i] = (byte) (key[i] ^ (byte) 0x36); |
|
1261 opad[i] = (byte) (key[i] ^ (byte) 0x5c); |
|
1262 i++; |
|
1263 } |
|
1264 while (i < 64) { |
|
1265 ipad[i] = (byte) 0x36; |
|
1266 opad[i] = (byte) 0x5c; |
|
1267 i++; |
|
1268 } |
|
1269 |
|
1270 // Very important: update the digest with the ipad buffer |
|
1271 md5.reset(); |
|
1272 md5.update(ipad); |
|
1273 |
|
1274 } |
|
1275 |
|
1276 /** Grab the current digest. This is the "answer". */ |
|
1277 byte[] getOutput() { |
|
1278 byte[] digest = md5.digest(); |
|
1279 md5.update(opad); |
|
1280 return md5.digest(digest); |
|
1281 } |
|
1282 |
|
1283 /** Update by adding a complete array */ |
|
1284 void update(byte[] input) { |
|
1285 md5.update(input); |
|
1286 } |
|
1287 |
|
1288 /** Update the algorithm */ |
|
1289 void update(byte[] input, int offset, int length) { |
|
1290 md5.update(input, offset, length); |
|
1291 } |
|
1292 |
|
1293 } |
|
1294 |
|
1295 public String generateType1Msg( |
|
1296 final String domain, |
|
1297 final String workstation) throws NTLMEngineException { |
|
1298 return getType1Message(workstation, domain); |
|
1299 } |
|
1300 |
|
1301 public String generateType3Msg( |
|
1302 final String username, |
|
1303 final String password, |
|
1304 final String domain, |
|
1305 final String workstation, |
|
1306 final String challenge) throws NTLMEngineException { |
|
1307 Type2Message t2m = new Type2Message(challenge); |
|
1308 return getType3Message( |
|
1309 username, |
|
1310 password, |
|
1311 workstation, |
|
1312 domain, |
|
1313 t2m.getChallenge(), |
|
1314 t2m.getFlags(), |
|
1315 t2m.getTarget(), |
|
1316 t2m.getTargetInfo()); |
|
1317 } |
|
1318 |
|
1319 } |