1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package davmail.http;
21
22
23 import org.apache.commons.codec.binary.Base64;
24 import org.apache.http.Consts;
25 import org.apache.http.impl.auth.NTLMEngine;
26 import org.apache.http.impl.auth.NTLMEngineException;
27 import org.apache.log4j.Logger;
28
29 import javax.crypto.Cipher;
30 import javax.crypto.spec.SecretKeySpec;
31 import java.nio.charset.Charset;
32 import java.nio.charset.StandardCharsets;
33 import java.security.Key;
34 import java.security.MessageDigest;
35 import java.security.NoSuchAlgorithmException;
36 import java.security.SecureRandom;
37 import java.security.cert.Certificate;
38 import java.security.cert.CertificateEncodingException;
39 import java.security.cert.X509Certificate;
40 import java.util.Arrays;
41 import java.util.Locale;
42
43
44
45
46
47
48 final class DavMailNTLMEngineImpl implements NTLMEngine {
49
50 static final Logger LOGGER = Logger.getLogger("davmail.http.DavMailNTLMEngineImpl");
51
52
53 private static final Charset UNICODE_LITTLE_UNMARKED = StandardCharsets.UTF_16LE;
54
55 private static final Charset DEFAULT_CHARSET = Consts.ASCII;
56
57
58
59
60
61
62 static final int FLAG_REQUEST_UNICODE_ENCODING = 0x00000001;
63 static final int FLAG_REQUEST_OEM_ENCODING = 0x00000002;
64 static final int FLAG_REQUEST_TARGET = 0x00000004;
65 static final int FLAG_REQUEST_SIGN = 0x00000010;
66 static final int FLAG_REQUEST_SEAL = 0x00000020;
67 static final int FLAG_REQUEST_LAN_MANAGER_KEY = 0x00000080;
68 static final int FLAG_REQUEST_NTLMv1 = 0x00000200;
69 static final int FLAG_DOMAIN_PRESENT = 0x00001000;
70 static final int FLAG_WORKSTATION_PRESENT = 0x00002000;
71 static final int FLAG_REQUEST_ALWAYS_SIGN = 0x00008000;
72 static final int FLAG_REQUEST_NTLM2_SESSION = 0x00080000;
73 static final int FLAG_REQUEST_VERSION = 0x02000000;
74 static final int FLAG_TARGETINFO_PRESENT = 0x00800000;
75 static final int FLAG_REQUEST_128BIT_KEY_EXCH = 0x20000000;
76 static final int FLAG_REQUEST_EXPLICIT_KEY_EXCH = 0x40000000;
77 static final int FLAG_REQUEST_56BIT_ENCRYPTION = 0x80000000;
78
79
80
81 static final int MSV_AV_EOL = 0x0000;
82 static final int MSV_AV_NB_COMPUTER_NAME = 0x0001;
83 static final int MSV_AV_NB_DOMAIN_NAME = 0x0002;
84 static final int MSV_AV_DNS_COMPUTER_NAME = 0x0003;
85 static final int MSV_AV_DNS_DOMAIN_NAME = 0x0004;
86 static final int MSV_AV_DNS_TREE_NAME = 0x0005;
87 static final int MSV_AV_FLAGS = 0x0006;
88 static final int MSV_AV_TIMESTAMP = 0x0007;
89 static final int MSV_AV_SINGLE_HOST = 0x0008;
90 static final int MSV_AV_TARGET_NAME = 0x0009;
91 static final int MSV_AV_CHANNEL_BINDINGS = 0x000A;
92
93 static final int MSV_AV_FLAGS_ACCOUNT_AUTH_CONSTAINED = 0x00000001;
94 static final int MSV_AV_FLAGS_MIC = 0x00000002;
95 static final int MSV_AV_FLAGS_UNTRUSTED_TARGET_SPN = 0x00000004;
96
97 private static final SecureRandom RNG;
98
99 static {
100 try {
101 RNG = java.security.SecureRandom.getInstance("SHA1PRNG");
102 } catch (final NoSuchAlgorithmException e) {
103 throw new RuntimeException(e.getMessage(), e);
104 }
105 }
106
107
108 private static final byte[] SIGNATURE = getNullTerminatedAsciiString("NTLMSSP");
109
110
111 private static final byte[] MAGIC_TLS_SERVER_ENDPOINT = "tls-server-end-point:".getBytes(Consts.ASCII);
112
113 private static byte[] getNullTerminatedAsciiString(final String source) {
114 final byte[] bytesWithoutNull = source.getBytes(Consts.ASCII);
115 final byte[] target = new byte[bytesWithoutNull.length + 1];
116 System.arraycopy(bytesWithoutNull, 0, target, 0, bytesWithoutNull.length);
117 target[bytesWithoutNull.length] = (byte) 0x00;
118 return target;
119 }
120
121 private static final String TYPE_1_MESSAGE = new DavMailNTLMEngineImpl.Type1Message().getResponse();
122
123 Certificate peerServerCertificate = null;
124
125 DavMailNTLMEngineImpl() {
126 }
127
128
129
130
131
132 public void setPeerServerCertificate(Certificate peerServerCertificate) {
133 this.peerServerCertificate = peerServerCertificate;
134 }
135
136
137
138
139
140
141
142
143
144
145
146
147 static String getType1Message(final String host, final String domain) {
148
149 return TYPE_1_MESSAGE;
150 }
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172 static String getType3Message(final String user, final String password, final String host, final String domain,
173 final byte[] nonce, final int type2Flags, final String target, final byte[] targetInformation)
174 throws NTLMEngineException {
175 return new DavMailNTLMEngineImpl.Type3Message(domain, host, user, password, nonce, type2Flags, target,
176 targetInformation).getResponse();
177 }
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199 static String getType3Message(final String user, final String password, final String host, final String domain,
200 final byte[] nonce, final int type2Flags, final String target, final byte[] targetInformation,
201 final Certificate peerServerCertificate, final byte[] type1Message, final byte[] type2Message)
202 throws NTLMEngineException {
203 return new Type3Message(domain, host, user, password, nonce, type2Flags, target,
204 targetInformation, peerServerCertificate, type1Message, type2Message).getResponse();
205 }
206
207 private static int readULong(final byte[] src, final int index) {
208 if (src.length < index + 4) {
209 return 0;
210 }
211 return (src[index] & 0xff) | ((src[index + 1] & 0xff) << 8)
212 | ((src[index + 2] & 0xff) << 16) | ((src[index + 3] & 0xff) << 24);
213 }
214
215 private static int readUShort(final byte[] src, final int index) {
216 if (src.length < index + 2) {
217 return 0;
218 }
219 return (src[index] & 0xff) | ((src[index + 1] & 0xff) << 8);
220 }
221
222 private static byte[] readSecurityBuffer(final byte[] src, final int index) {
223 final int length = readUShort(src, index);
224 final int offset = readULong(src, index + 4);
225 if (src.length < offset + length) {
226 return new byte[length];
227 }
228 final byte[] buffer = new byte[length];
229 System.arraycopy(src, offset, buffer, 0, length);
230 return buffer;
231 }
232
233
234 private static byte[] makeRandomChallenge() {
235 final byte[] rval = new byte[8];
236 RNG.nextBytes(rval);
237 return rval;
238 }
239
240
241 private static byte[] makeSecondaryKey() {
242 final byte[] rval = new byte[16];
243 RNG.nextBytes(rval);
244 return rval;
245 }
246
247 protected static class CipherGen {
248
249 protected final long currentTime;
250
251 protected final String domain;
252 protected final String user;
253 protected final String password;
254 protected final byte[] challenge;
255 protected final String target;
256 protected final byte[] targetInformation;
257
258
259 protected byte[] clientChallenge;
260 protected byte[] clientChallenge2;
261 protected byte[] secondaryKey;
262 protected byte[] timestamp;
263
264
265 protected byte[] lmHash = null;
266 protected byte[] lmResponse = null;
267 protected byte[] ntlmHash = null;
268 protected byte[] ntlmResponse = null;
269 protected byte[] ntlmv2Hash = null;
270 protected byte[] lmv2Hash = null;
271 protected byte[] lmv2Response = null;
272 protected byte[] ntlmv2Blob = null;
273 protected byte[] ntlmv2Response = null;
274 protected byte[] ntlm2SessionResponse = null;
275 protected byte[] lm2SessionResponse = null;
276 protected byte[] lmUserSessionKey = null;
277 protected byte[] ntlmUserSessionKey = null;
278 protected byte[] ntlmv2UserSessionKey = null;
279 protected byte[] ntlm2SessionResponseUserSessionKey = null;
280 protected byte[] lanManagerSessionKey = null;
281
282 public CipherGen(final long currentTime,
283 final String domain, final String user, final String password,
284 final byte[] challenge, final String target, final byte[] targetInformation,
285 final byte[] clientChallenge, final byte[] clientChallenge2,
286 final byte[] secondaryKey, final byte[] timestamp) {
287 this.currentTime = currentTime;
288
289 this.domain = domain;
290 this.target = target;
291 this.user = user;
292 this.password = password;
293 this.challenge = challenge;
294 this.targetInformation = targetInformation;
295 this.clientChallenge = clientChallenge;
296 this.clientChallenge2 = clientChallenge2;
297 this.secondaryKey = secondaryKey;
298 this.timestamp = timestamp;
299 }
300
301 public CipherGen(final long currentTime,
302 final String domain,
303 final String user,
304 final String password,
305 final byte[] challenge,
306 final String target,
307 final byte[] targetInformation) {
308 this(currentTime, domain, user, password, challenge, target, targetInformation, null, null, null, null);
309 }
310
311
312 public byte[] getClientChallenge() {
313 if (clientChallenge == null) {
314 clientChallenge = makeRandomChallenge();
315 }
316 return clientChallenge;
317 }
318
319
320 public byte[] getClientChallenge2() {
321 if (clientChallenge2 == null) {
322 clientChallenge2 = makeRandomChallenge();
323 }
324 return clientChallenge2;
325 }
326
327
328 public byte[] getSecondaryKey() {
329 if (secondaryKey == null) {
330 secondaryKey = makeSecondaryKey();
331 }
332 return secondaryKey;
333 }
334
335
336 public byte[] getLMHash()
337 throws NTLMEngineException {
338 if (lmHash == null) {
339 lmHash = lmHash(password);
340 }
341 return lmHash;
342 }
343
344
345 public byte[] getLMResponse()
346 throws NTLMEngineException {
347 if (lmResponse == null) {
348 lmResponse = lmResponse(getLMHash(), challenge);
349 }
350 return lmResponse;
351 }
352
353
354 public byte[] getNTLMHash() {
355 if (ntlmHash == null) {
356 ntlmHash = ntlmHash(password);
357 }
358 return ntlmHash;
359 }
360
361
362 public byte[] getNTLMResponse()
363 throws NTLMEngineException {
364 if (ntlmResponse == null) {
365 ntlmResponse = lmResponse(getNTLMHash(), challenge);
366 }
367 return ntlmResponse;
368 }
369
370
371 public byte[] getLMv2Hash()
372 throws NTLMEngineException {
373 if (lmv2Hash == null) {
374 lmv2Hash = lmv2Hash(domain, user, getNTLMHash());
375 }
376 return lmv2Hash;
377 }
378
379
380 public byte[] getNTLMv2Hash()
381 throws NTLMEngineException {
382 if (ntlmv2Hash == null) {
383 ntlmv2Hash = ntlmv2Hash(domain, user, getNTLMHash());
384 }
385 return ntlmv2Hash;
386 }
387
388
389 public byte[] getTimestamp() {
390 if (timestamp == null) {
391 long time = this.currentTime;
392 time += 11644473600000L;
393 time *= 10000;
394
395 timestamp = new byte[8];
396 for (int i = 0; i < 8; i++) {
397 timestamp[i] = (byte) time;
398 time >>>= 8;
399 }
400 }
401 return timestamp;
402 }
403
404
405 public byte[] getNTLMv2Blob() {
406 if (ntlmv2Blob == null) {
407 ntlmv2Blob = createBlob(getClientChallenge2(), targetInformation, getTimestamp());
408 }
409 return ntlmv2Blob;
410 }
411
412
413 public byte[] getNTLMv2Response()
414 throws NTLMEngineException {
415 if (ntlmv2Response == null) {
416 ntlmv2Response = lmv2Response(getNTLMv2Hash(), challenge, getNTLMv2Blob());
417 }
418 return ntlmv2Response;
419 }
420
421
422 public byte[] getLMv2Response()
423 throws NTLMEngineException {
424 if (lmv2Response == null) {
425 lmv2Response = lmv2Response(getLMv2Hash(), challenge, getClientChallenge());
426 }
427 return lmv2Response;
428 }
429
430
431 public byte[] getNTLM2SessionResponse()
432 throws NTLMEngineException {
433 if (ntlm2SessionResponse == null) {
434 ntlm2SessionResponse = ntlm2SessionResponse(getNTLMHash(), challenge, getClientChallenge());
435 }
436 return ntlm2SessionResponse;
437 }
438
439
440 public byte[] getLM2SessionResponse() {
441 if (lm2SessionResponse == null) {
442 final byte[] clntChallenge = getClientChallenge();
443 lm2SessionResponse = new byte[24];
444 System.arraycopy(clntChallenge, 0, lm2SessionResponse, 0, clntChallenge.length);
445 Arrays.fill(lm2SessionResponse, clntChallenge.length, lm2SessionResponse.length, (byte) 0x00);
446 }
447 return lm2SessionResponse;
448 }
449
450
451 public byte[] getLMUserSessionKey()
452 throws NTLMEngineException {
453 if (lmUserSessionKey == null) {
454 lmUserSessionKey = new byte[16];
455 System.arraycopy(getLMHash(), 0, lmUserSessionKey, 0, 8);
456 Arrays.fill(lmUserSessionKey, 8, 16, (byte) 0x00);
457 }
458 return lmUserSessionKey;
459 }
460
461
462 public byte[] getNTLMUserSessionKey()
463 throws NTLMEngineException {
464 if (ntlmUserSessionKey == null) {
465 final DavMailNTLMEngineImpl.MD4 md4 = new DavMailNTLMEngineImpl.MD4();
466 md4.update(getNTLMHash());
467 ntlmUserSessionKey = md4.getOutput();
468 }
469 return ntlmUserSessionKey;
470 }
471
472
473 public byte[] getNTLMv2UserSessionKey()
474 throws NTLMEngineException {
475 if (ntlmv2UserSessionKey == null) {
476 final byte[] ntlmv2hash = getNTLMv2Hash();
477 final byte[] truncatedResponse = new byte[16];
478 System.arraycopy(getNTLMv2Response(), 0, truncatedResponse, 0, 16);
479 ntlmv2UserSessionKey = hmacMD5(truncatedResponse, ntlmv2hash);
480 }
481 return ntlmv2UserSessionKey;
482 }
483
484
485 public byte[] getNTLM2SessionResponseUserSessionKey()
486 throws NTLMEngineException {
487 if (ntlm2SessionResponseUserSessionKey == null) {
488 final byte[] ntlm2SessionResponseNonce = getLM2SessionResponse();
489 final byte[] sessionNonce = new byte[challenge.length + ntlm2SessionResponseNonce.length];
490 System.arraycopy(challenge, 0, sessionNonce, 0, challenge.length);
491 System.arraycopy(ntlm2SessionResponseNonce, 0, sessionNonce, challenge.length, ntlm2SessionResponseNonce.length);
492 ntlm2SessionResponseUserSessionKey = hmacMD5(sessionNonce, getNTLMUserSessionKey());
493 }
494 return ntlm2SessionResponseUserSessionKey;
495 }
496
497
498 public byte[] getLanManagerSessionKey()
499 throws NTLMEngineException {
500 if (lanManagerSessionKey == null) {
501 try {
502 final byte[] keyBytes = new byte[14];
503 System.arraycopy(getLMHash(), 0, keyBytes, 0, 8);
504 Arrays.fill(keyBytes, 8, keyBytes.length, (byte) 0xbd);
505 final Key lowKey = createDESKey(keyBytes, 0);
506 final Key highKey = createDESKey(keyBytes, 7);
507 final byte[] truncatedResponse = new byte[8];
508 System.arraycopy(getLMResponse(), 0, truncatedResponse, 0, truncatedResponse.length);
509 Cipher des = Cipher.getInstance("DES/ECB/NoPadding");
510 des.init(Cipher.ENCRYPT_MODE, lowKey);
511 final byte[] lowPart = des.doFinal(truncatedResponse);
512 des = Cipher.getInstance("DES/ECB/NoPadding");
513 des.init(Cipher.ENCRYPT_MODE, highKey);
514 final byte[] highPart = des.doFinal(truncatedResponse);
515 lanManagerSessionKey = new byte[16];
516 System.arraycopy(lowPart, 0, lanManagerSessionKey, 0, lowPart.length);
517 System.arraycopy(highPart, 0, lanManagerSessionKey, lowPart.length, highPart.length);
518 } catch (final Exception e) {
519 throw new NTLMEngineException(e.getMessage(), e);
520 }
521 }
522 return lanManagerSessionKey;
523 }
524 }
525
526
527 static byte[] hmacMD5(final byte[] value, final byte[] key) {
528 final DavMailNTLMEngineImpl.HMACMD5 hmacMD5 = new DavMailNTLMEngineImpl.HMACMD5(key);
529 hmacMD5.update(value);
530 return hmacMD5.getOutput();
531 }
532
533
534 static byte[] RC4(final byte[] value, final byte[] key)
535 throws NTLMEngineException {
536 try {
537 final Cipher rc4 = Cipher.getInstance("RC4");
538 rc4.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "RC4"));
539 return rc4.doFinal(value);
540 } catch (final Exception e) {
541 throw new NTLMEngineException(e.getMessage(), e);
542 }
543 }
544
545
546
547
548
549
550
551
552
553 static byte[] ntlm2SessionResponse(final byte[] ntlmHash, final byte[] challenge,
554 final byte[] clientChallenge) throws NTLMEngineException {
555 try {
556 final MessageDigest md5 = getMD5();
557 md5.update(challenge);
558 md5.update(clientChallenge);
559 final byte[] digest = md5.digest();
560
561 final byte[] sessionHash = new byte[8];
562 System.arraycopy(digest, 0, sessionHash, 0, 8);
563 return lmResponse(ntlmHash, sessionHash);
564 } catch (final NTLMEngineException e) {
565 throw e;
566 } catch (final Exception e) {
567 throw new NTLMEngineException(e.getMessage(), e);
568 }
569 }
570
571
572
573
574
575
576
577
578
579
580 private static byte[] lmHash(final String password) throws NTLMEngineException {
581 try {
582 final byte[] oemPassword = password.toUpperCase(Locale.ROOT).getBytes(Consts.ASCII);
583 final int length = Math.min(oemPassword.length, 14);
584 final byte[] keyBytes = new byte[14];
585 System.arraycopy(oemPassword, 0, keyBytes, 0, length);
586 final Key lowKey = createDESKey(keyBytes, 0);
587 final Key highKey = createDESKey(keyBytes, 7);
588 final byte[] magicConstant = "KGS!@#$%".getBytes(Consts.ASCII);
589 final Cipher des = Cipher.getInstance("DES/ECB/NoPadding");
590 des.init(Cipher.ENCRYPT_MODE, lowKey);
591 final byte[] lowHash = des.doFinal(magicConstant);
592 des.init(Cipher.ENCRYPT_MODE, highKey);
593 final byte[] highHash = des.doFinal(magicConstant);
594 final byte[] lmHash = new byte[16];
595 System.arraycopy(lowHash, 0, lmHash, 0, 8);
596 System.arraycopy(highHash, 0, lmHash, 8, 8);
597 return lmHash;
598 } catch (final Exception e) {
599 throw new NTLMEngineException(e.getMessage(), e);
600 }
601 }
602
603
604
605
606
607
608
609
610
611
612 private static byte[] ntlmHash(final String password) {
613 final byte[] unicodePassword = password.getBytes(UNICODE_LITTLE_UNMARKED);
614 final DavMailNTLMEngineImpl.MD4 md4 = new DavMailNTLMEngineImpl.MD4();
615 md4.update(unicodePassword);
616 return md4.getOutput();
617 }
618
619
620
621
622
623
624
625 private static byte[] lmv2Hash(final String domain, final String user, final byte[] ntlmHash)
626 throws NTLMEngineException {
627 final DavMailNTLMEngineImpl.HMACMD5 hmacMD5 = new DavMailNTLMEngineImpl.HMACMD5(ntlmHash);
628
629 hmacMD5.update(user.toUpperCase(Locale.ROOT).getBytes(UNICODE_LITTLE_UNMARKED));
630 if (domain != null) {
631 hmacMD5.update(domain.toUpperCase(Locale.ROOT).getBytes(UNICODE_LITTLE_UNMARKED));
632 }
633 return hmacMD5.getOutput();
634 }
635
636
637
638
639
640
641
642 private static byte[] ntlmv2Hash(final String domain, final String user, final byte[] ntlmHash) {
643 final DavMailNTLMEngineImpl.HMACMD5 hmacMD5 = new DavMailNTLMEngineImpl.HMACMD5(ntlmHash);
644
645 hmacMD5.update(user.toUpperCase(Locale.ROOT).getBytes(UNICODE_LITTLE_UNMARKED));
646 if (domain != null) {
647 hmacMD5.update(domain.getBytes(UNICODE_LITTLE_UNMARKED));
648 }
649 return hmacMD5.getOutput();
650 }
651
652
653
654
655
656
657
658
659
660
661
662 private static byte[] lmResponse(final byte[] hash, final byte[] challenge) throws NTLMEngineException {
663 try {
664 final byte[] keyBytes = new byte[21];
665 System.arraycopy(hash, 0, keyBytes, 0, 16);
666 final Key lowKey = createDESKey(keyBytes, 0);
667 final Key middleKey = createDESKey(keyBytes, 7);
668 final Key highKey = createDESKey(keyBytes, 14);
669 final Cipher des = Cipher.getInstance("DES/ECB/NoPadding");
670 des.init(Cipher.ENCRYPT_MODE, lowKey);
671 final byte[] lowResponse = des.doFinal(challenge);
672 des.init(Cipher.ENCRYPT_MODE, middleKey);
673 final byte[] middleResponse = des.doFinal(challenge);
674 des.init(Cipher.ENCRYPT_MODE, highKey);
675 final byte[] highResponse = des.doFinal(challenge);
676 final byte[] lmResponse = new byte[24];
677 System.arraycopy(lowResponse, 0, lmResponse, 0, 8);
678 System.arraycopy(middleResponse, 0, lmResponse, 8, 8);
679 System.arraycopy(highResponse, 0, lmResponse, 16, 8);
680 return lmResponse;
681 } catch (final Exception e) {
682 throw new NTLMEngineException(e.getMessage(), e);
683 }
684 }
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700 private static byte[] lmv2Response(final byte[] hash, final byte[] challenge, final byte[] clientData) {
701 final DavMailNTLMEngineImpl.HMACMD5 hmacMD5 = new DavMailNTLMEngineImpl.HMACMD5(hash);
702 hmacMD5.update(challenge);
703 hmacMD5.update(clientData);
704 final byte[] mac = hmacMD5.getOutput();
705 final byte[] lmv2Response = new byte[mac.length + clientData.length];
706 System.arraycopy(mac, 0, lmv2Response, 0, mac.length);
707 System.arraycopy(clientData, 0, lmv2Response, mac.length, clientData.length);
708 return lmv2Response;
709 }
710
711 private static byte[] encodeLong(final int value) {
712 final byte[] enc = new byte[4];
713 encodeLong(enc, 0, value);
714 return enc;
715 }
716
717 private static void encodeLong(final byte[] buf, final int offset, final int value) {
718 buf[offset ] = (byte) (value & 0xff);
719 buf[offset + 1] = (byte) (value >> 8 & 0xff);
720 buf[offset + 2] = (byte) (value >> 16 & 0xff);
721 buf[offset + 3] = (byte) (value >> 24 & 0xff);
722 }
723
724
725
726
727
728
729
730
731
732
733
734
735 private static byte[] createBlob(final byte[] clientChallenge, final byte[] targetInformation, final byte[] timestamp) {
736 final byte[] blobSignature = new byte[]{(byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00};
737 final byte[] reserved = new byte[]{(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00};
738 final byte[] unknown1 = new byte[]{(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00};
739 final byte[] unknown2 = new byte[]{(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00};
740 final byte[] blob = new byte[blobSignature.length + reserved.length + timestamp.length + 8
741 + unknown1.length + targetInformation.length + unknown2.length];
742 int offset = 0;
743 System.arraycopy(blobSignature, 0, blob, offset, blobSignature.length);
744 offset += blobSignature.length;
745 System.arraycopy(reserved, 0, blob, offset, reserved.length);
746 offset += reserved.length;
747 System.arraycopy(timestamp, 0, blob, offset, timestamp.length);
748 offset += timestamp.length;
749 System.arraycopy(clientChallenge, 0, blob, offset, 8);
750 offset += 8;
751 System.arraycopy(unknown1, 0, blob, offset, unknown1.length);
752 offset += unknown1.length;
753 System.arraycopy(targetInformation, 0, blob, offset, targetInformation.length);
754 offset += targetInformation.length;
755 System.arraycopy(unknown2, 0, blob, offset, unknown2.length);
756 offset += unknown2.length;
757 return blob;
758 }
759
760
761
762
763
764
765
766
767
768
769
770
771
772 private static Key createDESKey(final byte[] bytes, final int offset) {
773 final byte[] keyBytes = new byte[7];
774 System.arraycopy(bytes, offset, keyBytes, 0, 7);
775 final byte[] material = new byte[8];
776 material[0] = keyBytes[0];
777 material[1] = (byte) (keyBytes[0] << 7 | (keyBytes[1] & 0xff) >>> 1);
778 material[2] = (byte) (keyBytes[1] << 6 | (keyBytes[2] & 0xff) >>> 2);
779 material[3] = (byte) (keyBytes[2] << 5 | (keyBytes[3] & 0xff) >>> 3);
780 material[4] = (byte) (keyBytes[3] << 4 | (keyBytes[4] & 0xff) >>> 4);
781 material[5] = (byte) (keyBytes[4] << 3 | (keyBytes[5] & 0xff) >>> 5);
782 material[6] = (byte) (keyBytes[5] << 2 | (keyBytes[6] & 0xff) >>> 6);
783 material[7] = (byte) (keyBytes[6] << 1);
784 oddParity(material);
785 return new SecretKeySpec(material, "DES");
786 }
787
788
789
790
791
792
793
794 private static void oddParity(final byte[] bytes) {
795 for (int i = 0; i < bytes.length; i++) {
796 final byte b = bytes[i];
797 final boolean needsParity = (((b >>> 7) ^ (b >>> 6) ^ (b >>> 5) ^ (b >>> 4) ^ (b >>> 3)
798 ^ (b >>> 2) ^ (b >>> 1)) & 0x01) == 0;
799 if (needsParity) {
800 bytes[i] |= (byte) 0x01;
801 } else {
802 bytes[i] &= (byte) 0xfe;
803 }
804 }
805 }
806
807
808
809
810
811
812 private static Charset getCharset(final int flags) {
813 if ((flags & FLAG_REQUEST_UNICODE_ENCODING) == 0) {
814 return DEFAULT_CHARSET;
815 }
816 return UNICODE_LITTLE_UNMARKED;
817 }
818
819
820 private static String stripDotSuffix(final String value) {
821 if (value == null) {
822 return null;
823 }
824 final int index = value.indexOf('.');
825 if (index != -1) {
826 return value.substring(0, index);
827 }
828 return value;
829 }
830
831
832 private static String convertHost(final String host) {
833 return stripDotSuffix(host);
834 }
835
836
837 private static String convertDomain(final String domain) {
838 return stripDotSuffix(domain);
839 }
840
841
842 static class NTLMMessage {
843
844 protected byte[] messageContents = null;
845
846
847 protected int currentOutputPosition = 0;
848
849
850 NTLMMessage() {
851 }
852
853
854 NTLMMessage(final String messageBody, final int expectedType) throws NTLMEngineException {
855 this(Base64.decodeBase64(messageBody.getBytes(DEFAULT_CHARSET)), expectedType);
856 }
857
858
859 NTLMMessage(final byte[] message, final int expectedType) throws NTLMEngineException {
860 messageContents = message;
861
862 if (messageContents.length < SIGNATURE.length) {
863 throw new NTLMEngineException("NTLM message decoding error - packet too short");
864 }
865 int i = 0;
866 while (i < SIGNATURE.length) {
867 if (messageContents[i] != SIGNATURE[i]) {
868 throw new NTLMEngineException(
869 "NTLM message expected - instead got unrecognized bytes");
870 }
871 i++;
872 }
873
874
875 final int type = readULong(SIGNATURE.length);
876 if (type != expectedType) {
877 throw new NTLMEngineException("NTLM type " + expectedType
878 + " message expected - instead got type " + type);
879 }
880
881 currentOutputPosition = messageContents.length;
882 }
883
884
885
886
887
888 protected int getPreambleLength() {
889 return SIGNATURE.length + 4;
890 }
891
892
893 protected int getMessageLength() {
894 return currentOutputPosition;
895 }
896
897
898 protected byte readByte(final int position) throws NTLMEngineException {
899 if (messageContents.length < position + 1) {
900 throw new NTLMEngineException("NTLM: Message too short");
901 }
902 return messageContents[position];
903 }
904
905
906 protected void readBytes(final byte[] buffer, final int position) throws NTLMEngineException {
907 if (messageContents.length < position + buffer.length) {
908 throw new NTLMEngineException("NTLM: Message too short");
909 }
910 System.arraycopy(messageContents, position, buffer, 0, buffer.length);
911 }
912
913
914 protected int readUShort(final int position) {
915 return DavMailNTLMEngineImpl.readUShort(messageContents, position);
916 }
917
918
919 protected int readULong(final int position) {
920 return DavMailNTLMEngineImpl.readULong(messageContents, position);
921 }
922
923
924 protected byte[] readSecurityBuffer(final int position) {
925 return DavMailNTLMEngineImpl.readSecurityBuffer(messageContents, position);
926 }
927
928
929
930
931
932
933
934
935
936 protected void prepareResponse(final int maxlength, final int messageType) {
937 messageContents = new byte[maxlength];
938 currentOutputPosition = 0;
939 addBytes(SIGNATURE);
940 addULong(messageType);
941 }
942
943
944
945
946
947
948
949 protected void addByte(final byte b) {
950 messageContents[currentOutputPosition] = b;
951 currentOutputPosition++;
952 }
953
954
955
956
957
958
959
960 protected void addBytes(final byte[] bytes) {
961 if (bytes == null) {
962 return;
963 }
964 for (final byte b : bytes) {
965 messageContents[currentOutputPosition] = b;
966 currentOutputPosition++;
967 }
968 }
969
970
971 protected void addUShort(final int value) {
972 addByte((byte) (value & 0xff));
973 addByte((byte) (value >> 8 & 0xff));
974 }
975
976
977 protected void addULong(final int value) {
978 addByte((byte) (value & 0xff));
979 addByte((byte) (value >> 8 & 0xff));
980 addByte((byte) (value >> 16 & 0xff));
981 addByte((byte) (value >> 24 & 0xff));
982 }
983
984
985
986
987
988
989
990 public String getResponse() {
991 return new String(Base64.encodeBase64(getBytes()), Consts.ASCII);
992 }
993
994 public byte[] getBytes() {
995 if (messageContents == null) {
996 buildMessage();
997 }
998
999 if (messageContents.length > currentOutputPosition) {
1000 final byte[] tmp = new byte[currentOutputPosition];
1001 System.arraycopy(messageContents, 0, tmp, 0, currentOutputPosition);
1002 messageContents = tmp;
1003 }
1004 return messageContents;
1005 }
1006
1007 protected void buildMessage() {
1008 throw new RuntimeException("Message builder not implemented for " + getClass().getName());
1009 }
1010 }
1011
1012
1013 static class Type1Message extends DavMailNTLMEngineImpl.NTLMMessage {
1014
1015 private final byte[] hostBytes;
1016 private final byte[] domainBytes;
1017 private final int flags;
1018
1019 Type1Message(final String domain, final String host) {
1020 this(domain, host, null);
1021 }
1022
1023 Type1Message(final String domain, final String host, final Integer flags) {
1024 super();
1025 this.flags = ((flags == null) ? getDefaultFlags() : flags);
1026
1027
1028 final String unqualifiedHost = convertHost(host);
1029
1030 final String unqualifiedDomain = convertDomain(domain);
1031
1032 hostBytes = unqualifiedHost != null ?
1033 unqualifiedHost.getBytes(UNICODE_LITTLE_UNMARKED) : null;
1034 domainBytes = unqualifiedDomain != null ?
1035 unqualifiedDomain.toUpperCase(Locale.ROOT).getBytes(UNICODE_LITTLE_UNMARKED) : null;
1036 }
1037
1038 Type1Message() {
1039 super();
1040 hostBytes = null;
1041 domainBytes = null;
1042 flags = getDefaultFlags();
1043 }
1044
1045 private int getDefaultFlags() {
1046 return
1047
1048
1049
1050
1051
1052 FLAG_REQUEST_NTLMv1 |
1053 FLAG_REQUEST_NTLM2_SESSION |
1054
1055
1056 FLAG_REQUEST_VERSION |
1057
1058
1059 FLAG_REQUEST_ALWAYS_SIGN |
1060
1061 FLAG_REQUEST_SIGN |
1062
1063
1064 FLAG_REQUEST_128BIT_KEY_EXCH |
1065 FLAG_REQUEST_56BIT_ENCRYPTION |
1066
1067
1068 FLAG_REQUEST_UNICODE_ENCODING;
1069
1070 }
1071
1072
1073
1074
1075
1076 @Override
1077 protected void buildMessage() {
1078 int domainBytesLength = 0;
1079 if (domainBytes != null) {
1080 domainBytesLength = domainBytes.length;
1081 }
1082 int hostBytesLength = 0;
1083 if (hostBytes != null) {
1084 hostBytesLength = hostBytes.length;
1085 }
1086
1087
1088
1089 final int finalLength = 32 + 8 + hostBytesLength + domainBytesLength;
1090
1091
1092
1093 prepareResponse(finalLength, 1);
1094
1095
1096 addULong(flags);
1097
1098
1099 addUShort(domainBytesLength);
1100 addUShort(domainBytesLength);
1101
1102
1103 addULong(hostBytesLength + 32 + 8);
1104
1105
1106 addUShort(hostBytesLength);
1107 addUShort(hostBytesLength);
1108
1109
1110 addULong(32 + 8);
1111
1112
1113 addUShort(0x0105);
1114
1115 addULong(2600);
1116
1117 addUShort(0x0f00);
1118
1119
1120 if (hostBytes != null) {
1121 addBytes(hostBytes);
1122 }
1123
1124 if (domainBytes != null) {
1125 addBytes(domainBytes);
1126 }
1127 }
1128
1129 }
1130
1131
1132 static class Type2Message extends NTLMMessage {
1133 protected final byte[] challenge;
1134 protected String target;
1135 protected byte[] targetInfo;
1136 protected final int flags;
1137
1138 Type2Message(final String messageBody) throws NTLMEngineException {
1139 this(Base64.decodeBase64(messageBody.getBytes(DEFAULT_CHARSET)));
1140 }
1141
1142 Type2Message(final byte[] message) throws NTLMEngineException {
1143 super(message, 2);
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160 challenge = new byte[8];
1161 readBytes(challenge, 24);
1162
1163 flags = super.readULong(20);
1164
1165
1166 target = null;
1167
1168
1169
1170 if (getMessageLength() >= 12 + 8) {
1171 final byte[] bytes = super.readSecurityBuffer(12);
1172 if (bytes.length != 0) {
1173 target = new String(bytes, getCharset(flags));
1174 }
1175 }
1176
1177
1178 targetInfo = null;
1179
1180 if (getMessageLength() >= 40 + 8) {
1181 final byte[] bytes = super.readSecurityBuffer(40);
1182 if (bytes.length != 0) {
1183 targetInfo = bytes;
1184 }
1185 }
1186 }
1187
1188
1189 byte[] getChallenge() {
1190 return challenge;
1191 }
1192
1193
1194 String getTarget() {
1195 return target;
1196 }
1197
1198
1199 byte[] getTargetInfo() {
1200 return targetInfo;
1201 }
1202
1203
1204 int getFlags() {
1205 return flags;
1206 }
1207
1208 }
1209
1210
1211 static class Type3Message extends DavMailNTLMEngineImpl.NTLMMessage {
1212
1213 protected final byte[] type1Message;
1214 protected final byte[] type2Message;
1215
1216 protected final int type2Flags;
1217
1218 protected final byte[] domainBytes;
1219 protected final byte[] hostBytes;
1220 protected final byte[] userBytes;
1221
1222 protected byte[] lmResp;
1223 protected byte[] ntResp;
1224 protected final byte[] sessionKey;
1225 protected final byte[] exportedSessionKey;
1226
1227 protected final boolean computeMic;
1228
1229
1230
1231 Type3Message(final String domain,
1232 final String host,
1233 final String user,
1234 final String password,
1235 final byte[] nonce,
1236 final int type2Flags,
1237 final String target,
1238 final byte[] targetInformation)
1239 throws NTLMEngineException {
1240 this(domain, host, user, password, nonce, type2Flags, target, targetInformation, null, null, null);
1241 }
1242
1243
1244
1245 Type3Message(final long currentTime,
1246 final String domain,
1247 final String host,
1248 final String user,
1249 final String password,
1250 final byte[] nonce,
1251 final int type2Flags,
1252 final String target,
1253 final byte[] targetInformation)
1254 throws NTLMEngineException {
1255 this(currentTime, domain, host, user, password, nonce, type2Flags, target, targetInformation, null, null, null);
1256 }
1257
1258
1259 Type3Message(final String domain,
1260 final String host,
1261 final String user,
1262 final String password,
1263 final byte[] nonce,
1264 final int type2Flags,
1265 final String target,
1266 final byte[] targetInformation,
1267 final Certificate peerServerCertificate,
1268 final byte[] type1Message,
1269 final byte[] type2Message)
1270 throws NTLMEngineException {
1271 this(System.currentTimeMillis(), domain, host, user, password, nonce, type2Flags, target, targetInformation, peerServerCertificate, type1Message, type2Message);
1272 }
1273
1274
1275 Type3Message(final long currentTime,
1276 final String domain,
1277 final String host,
1278 final String user,
1279 final String password,
1280 final byte[] nonce,
1281 final int type2Flags,
1282 final String target,
1283 final byte[] targetInformation,
1284 final Certificate peerServerCertificate,
1285 final byte[] type1Message,
1286 final byte[] type2Message)
1287 throws NTLMEngineException {
1288
1289
1290 this.type2Flags = type2Flags;
1291 this.type1Message = type1Message;
1292 this.type2Message = type2Message;
1293
1294
1295 final String unqualifiedHost = convertHost(host);
1296
1297 final String unqualifiedDomain = convertDomain(domain);
1298
1299 byte[] responseTargetInformation = targetInformation;
1300 if (peerServerCertificate != null) {
1301 responseTargetInformation = addGssMicAvsToTargetInfo(targetInformation, peerServerCertificate);
1302 computeMic = true;
1303 LOGGER.debug("Included certificate info for channel binding subject:"+((X509Certificate)peerServerCertificate).getSubjectX500Principal());
1304 } else {
1305 computeMic = false;
1306 LOGGER.debug("Certificate info not available for channel binding");
1307 }
1308
1309
1310 final DavMailNTLMEngineImpl.CipherGen gen = new DavMailNTLMEngineImpl.CipherGen(currentTime,
1311 unqualifiedDomain,
1312 user,
1313 password,
1314 nonce,
1315 target,
1316 responseTargetInformation);
1317
1318
1319
1320 byte[] userSessionKey;
1321 try {
1322
1323
1324 if (((type2Flags & FLAG_TARGETINFO_PRESENT) != 0) &&
1325 targetInformation != null && target != null) {
1326
1327 ntResp = gen.getNTLMv2Response();
1328 lmResp = gen.getLMv2Response();
1329 if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) {
1330 userSessionKey = gen.getLanManagerSessionKey();
1331 } else {
1332 userSessionKey = gen.getNTLMv2UserSessionKey();
1333 }
1334 } else {
1335
1336 if ((type2Flags & FLAG_REQUEST_NTLM2_SESSION) != 0) {
1337
1338 ntResp = gen.getNTLM2SessionResponse();
1339 lmResp = gen.getLM2SessionResponse();
1340 if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) {
1341 userSessionKey = gen.getLanManagerSessionKey();
1342 } else {
1343 userSessionKey = gen.getNTLM2SessionResponseUserSessionKey();
1344 }
1345 } else {
1346 ntResp = gen.getNTLMResponse();
1347 lmResp = gen.getLMResponse();
1348 if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) {
1349 userSessionKey = gen.getLanManagerSessionKey();
1350 } else {
1351 userSessionKey = gen.getNTLMUserSessionKey();
1352 }
1353 }
1354 }
1355 } catch (final NTLMEngineException e) {
1356
1357
1358 ntResp = new byte[0];
1359 lmResp = gen.getLMResponse();
1360 if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) {
1361 userSessionKey = gen.getLanManagerSessionKey();
1362 } else {
1363 userSessionKey = gen.getLMUserSessionKey();
1364 }
1365 }
1366
1367 if ((type2Flags & FLAG_REQUEST_SIGN) != 0) {
1368 if ((type2Flags & FLAG_REQUEST_EXPLICIT_KEY_EXCH) != 0) {
1369 exportedSessionKey = gen.getSecondaryKey();
1370 sessionKey = RC4(exportedSessionKey, userSessionKey);
1371 } else {
1372 sessionKey = userSessionKey;
1373 exportedSessionKey = sessionKey;
1374 }
1375 } else {
1376 if (computeMic) {
1377 throw new NTLMEngineException("Cannot sign/seal: no exported session key");
1378 }
1379 sessionKey = null;
1380 exportedSessionKey = null;
1381 }
1382 final Charset charset = getCharset(type2Flags);
1383 hostBytes = unqualifiedHost != null ? unqualifiedHost.getBytes(charset) : null;
1384 domainBytes = unqualifiedDomain != null ? unqualifiedDomain
1385 .toUpperCase(Locale.ROOT).getBytes(charset) : null;
1386 userBytes = user.getBytes(charset);
1387 }
1388
1389 public byte[] getEncryptedRandomSessionKey() {
1390 return sessionKey;
1391 }
1392
1393 public byte[] getExportedSessionKey() {
1394 return exportedSessionKey;
1395 }
1396
1397
1398 @Override
1399 protected void buildMessage() {
1400 final int ntRespLen = ntResp.length;
1401 final int lmRespLen = lmResp.length;
1402
1403 final int domainLen = domainBytes != null ? domainBytes.length : 0;
1404 final int hostLen = hostBytes != null ? hostBytes.length : 0;
1405 final int userLen = userBytes.length;
1406 final int sessionKeyLen;
1407 if (sessionKey != null) {
1408 sessionKeyLen = sessionKey.length;
1409 } else {
1410 sessionKeyLen = 0;
1411 }
1412
1413
1414 final int lmRespOffset = 72 +
1415 (computeMic ? 16 : 0);
1416 final int ntRespOffset = lmRespOffset + lmRespLen;
1417 final int domainOffset = ntRespOffset + ntRespLen;
1418 final int userOffset = domainOffset + domainLen;
1419 final int hostOffset = userOffset + userLen;
1420 final int sessionKeyOffset = hostOffset + hostLen;
1421 final int finalLength = sessionKeyOffset + sessionKeyLen;
1422
1423
1424 prepareResponse(finalLength, 3);
1425
1426
1427 addUShort(lmRespLen);
1428 addUShort(lmRespLen);
1429
1430
1431 addULong(lmRespOffset);
1432
1433
1434 addUShort(ntRespLen);
1435 addUShort(ntRespLen);
1436
1437
1438 addULong(ntRespOffset);
1439
1440
1441 addUShort(domainLen);
1442 addUShort(domainLen);
1443
1444
1445 addULong(domainOffset);
1446
1447
1448 addUShort(userLen);
1449 addUShort(userLen);
1450
1451
1452 addULong(userOffset);
1453
1454
1455 addUShort(hostLen);
1456 addUShort(hostLen);
1457
1458
1459 addULong(hostOffset);
1460
1461
1462 addUShort(sessionKeyLen);
1463 addUShort(sessionKeyLen);
1464
1465
1466 addULong(sessionKeyOffset);
1467
1468
1469 addULong(
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496 type2Flags
1497 );
1498
1499
1500 addUShort(0x0105);
1501
1502 addULong(2600);
1503
1504 addUShort(0x0f00);
1505
1506 int micPosition = -1;
1507 if (computeMic) {
1508 micPosition = currentOutputPosition;
1509 currentOutputPosition += 16;
1510 }
1511
1512
1513 addBytes(lmResp);
1514 addBytes(ntResp);
1515 addBytes(domainBytes);
1516 addBytes(userBytes);
1517 addBytes(hostBytes);
1518 if (sessionKey != null) {
1519 addBytes(sessionKey);
1520 }
1521
1522
1523
1524 if (computeMic) {
1525
1526 final DavMailNTLMEngineImpl.HMACMD5 hmacMD5 = new HMACMD5(exportedSessionKey);
1527 hmacMD5.update(type1Message);
1528 hmacMD5.update(type2Message);
1529 hmacMD5.update(messageContents);
1530 final byte[] mic = hmacMD5.getOutput();
1531 System.arraycopy(mic, 0, messageContents, micPosition, mic.length);
1532 }
1533 }
1534
1535
1536
1537
1538
1539 private byte[] addGssMicAvsToTargetInfo(final byte[] originalTargetInfo,
1540 final Certificate peerServerCertificate) throws NTLMEngineException {
1541 final byte[] newTargetInfo = new byte[originalTargetInfo.length + 8 + 20];
1542 final int appendLength = originalTargetInfo.length - 4;
1543 System.arraycopy(originalTargetInfo, 0, newTargetInfo, 0, appendLength);
1544 writeUShort(newTargetInfo, MSV_AV_FLAGS, appendLength);
1545 writeUShort(newTargetInfo, 4, appendLength + 2);
1546 writeULong(newTargetInfo, MSV_AV_FLAGS_MIC, appendLength + 4);
1547 writeUShort(newTargetInfo, MSV_AV_CHANNEL_BINDINGS, appendLength + 8);
1548 writeUShort(newTargetInfo, 16, appendLength + 10);
1549
1550 final byte[] channelBindingsHash;
1551 try {
1552 final byte[] certBytes = peerServerCertificate.getEncoded();
1553 final MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
1554 final byte[] certHashBytes = sha256.digest(certBytes);
1555 final byte[] channelBindingStruct = new byte[16 + 4 + MAGIC_TLS_SERVER_ENDPOINT.length
1556 + certHashBytes.length];
1557 writeULong(channelBindingStruct, 0x00000035, 16);
1558 System.arraycopy(MAGIC_TLS_SERVER_ENDPOINT, 0, channelBindingStruct, 20,
1559 MAGIC_TLS_SERVER_ENDPOINT.length);
1560 System.arraycopy(certHashBytes, 0, channelBindingStruct, 20 + MAGIC_TLS_SERVER_ENDPOINT.length,
1561 certHashBytes.length);
1562 final MessageDigest md5 = getMD5();
1563 channelBindingsHash = md5.digest(channelBindingStruct);
1564 } catch (CertificateEncodingException | NoSuchAlgorithmException e) {
1565 throw new NTLMEngineException(e.getMessage(), e);
1566 }
1567
1568 System.arraycopy(channelBindingsHash, 0, newTargetInfo, appendLength + 12, 16);
1569 return newTargetInfo;
1570 }
1571
1572 }
1573
1574 static void writeUShort(final byte[] buffer, final int value, final int offset) {
1575 buffer[offset] = (byte) (value & 0xff);
1576 buffer[offset + 1] = (byte) (value >> 8 & 0xff);
1577 }
1578
1579 static void writeULong(final byte[] buffer, final int value, final int offset) {
1580 buffer[offset] = (byte) (value & 0xff);
1581 buffer[offset + 1] = (byte) (value >> 8 & 0xff);
1582 buffer[offset + 2] = (byte) (value >> 16 & 0xff);
1583 buffer[offset + 3] = (byte) (value >> 24 & 0xff);
1584 }
1585
1586 static int F(final int x, final int y, final int z) {
1587 return ((x & y) | (~x & z));
1588 }
1589
1590 static int G(final int x, final int y, final int z) {
1591 return ((x & y) | (x & z) | (y & z));
1592 }
1593
1594 static int H(final int x, final int y, final int z) {
1595 return (x ^ y ^ z);
1596 }
1597
1598 static int rotintlft(final int val, final int numbits) {
1599 return ((val << numbits) | (val >>> (32 - numbits)));
1600 }
1601
1602 static MessageDigest getMD5() {
1603 try {
1604 return MessageDigest.getInstance("MD5");
1605 } catch (final NoSuchAlgorithmException ex) {
1606 throw new RuntimeException("MD5 message digest doesn't seem to exist - fatal error: " + ex.getMessage(), ex);
1607 }
1608 }
1609
1610
1611
1612
1613
1614
1615
1616
1617 static class MD4 {
1618 protected int A = 0x67452301;
1619 protected int B = 0xefcdab89;
1620 protected int C = 0x98badcfe;
1621 protected int D = 0x10325476;
1622 protected long count = 0L;
1623 protected final byte[] dataBuffer = new byte[64];
1624
1625 MD4() {
1626 }
1627
1628 void update(final byte[] input) {
1629
1630
1631
1632 int curBufferPos = (int) (count & 63L);
1633 int inputIndex = 0;
1634 while (input.length - inputIndex + curBufferPos >= dataBuffer.length) {
1635
1636
1637
1638 final int transferAmt = dataBuffer.length - curBufferPos;
1639 System.arraycopy(input, inputIndex, dataBuffer, curBufferPos, transferAmt);
1640 count += transferAmt;
1641 curBufferPos = 0;
1642 inputIndex += transferAmt;
1643 processBuffer();
1644 }
1645
1646
1647
1648 if (inputIndex < input.length) {
1649 final int transferAmt = input.length - inputIndex;
1650 System.arraycopy(input, inputIndex, dataBuffer, curBufferPos, transferAmt);
1651 count += transferAmt;
1652 }
1653 }
1654
1655 byte[] getOutput() {
1656
1657
1658 final int bufferIndex = (int) (count & 63L);
1659 final int padLen = (bufferIndex < 56) ? (56 - bufferIndex) : (120 - bufferIndex);
1660 final byte[] postBytes = new byte[padLen + 8];
1661
1662
1663 postBytes[0] = (byte) 0x80;
1664
1665 for (int i = 0; i < 8; i++) {
1666 postBytes[padLen + i] = (byte) ((count * 8) >>> (8 * i));
1667 }
1668
1669
1670 update(postBytes);
1671
1672
1673 final byte[] result = new byte[16];
1674 writeULong(result, A, 0);
1675 writeULong(result, B, 4);
1676 writeULong(result, C, 8);
1677 writeULong(result, D, 12);
1678 return result;
1679 }
1680
1681 protected void processBuffer() {
1682
1683 final int[] d = new int[16];
1684
1685 for (int i = 0; i < 16; i++) {
1686 d[i] = (dataBuffer[i * 4] & 0xff) + ((dataBuffer[i * 4 + 1] & 0xff) << 8)
1687 + ((dataBuffer[i * 4 + 2] & 0xff) << 16)
1688 + ((dataBuffer[i * 4 + 3] & 0xff) << 24);
1689 }
1690
1691
1692 final int AA = A;
1693 final int BB = B;
1694 final int CC = C;
1695 final int DD = D;
1696 round1(d);
1697 round2(d);
1698 round3(d);
1699 A += AA;
1700 B += BB;
1701 C += CC;
1702 D += DD;
1703
1704 }
1705
1706 protected void round1(final int[] d) {
1707 A = rotintlft((A + F(B, C, D) + d[0]), 3);
1708 D = rotintlft((D + F(A, B, C) + d[1]), 7);
1709 C = rotintlft((C + F(D, A, B) + d[2]), 11);
1710 B = rotintlft((B + F(C, D, A) + d[3]), 19);
1711
1712 A = rotintlft((A + F(B, C, D) + d[4]), 3);
1713 D = rotintlft((D + F(A, B, C) + d[5]), 7);
1714 C = rotintlft((C + F(D, A, B) + d[6]), 11);
1715 B = rotintlft((B + F(C, D, A) + d[7]), 19);
1716
1717 A = rotintlft((A + F(B, C, D) + d[8]), 3);
1718 D = rotintlft((D + F(A, B, C) + d[9]), 7);
1719 C = rotintlft((C + F(D, A, B) + d[10]), 11);
1720 B = rotintlft((B + F(C, D, A) + d[11]), 19);
1721
1722 A = rotintlft((A + F(B, C, D) + d[12]), 3);
1723 D = rotintlft((D + F(A, B, C) + d[13]), 7);
1724 C = rotintlft((C + F(D, A, B) + d[14]), 11);
1725 B = rotintlft((B + F(C, D, A) + d[15]), 19);
1726 }
1727
1728 protected void round2(final int[] d) {
1729 A = rotintlft((A + G(B, C, D) + d[0] + 0x5a827999), 3);
1730 D = rotintlft((D + G(A, B, C) + d[4] + 0x5a827999), 5);
1731 C = rotintlft((C + G(D, A, B) + d[8] + 0x5a827999), 9);
1732 B = rotintlft((B + G(C, D, A) + d[12] + 0x5a827999), 13);
1733
1734 A = rotintlft((A + G(B, C, D) + d[1] + 0x5a827999), 3);
1735 D = rotintlft((D + G(A, B, C) + d[5] + 0x5a827999), 5);
1736 C = rotintlft((C + G(D, A, B) + d[9] + 0x5a827999), 9);
1737 B = rotintlft((B + G(C, D, A) + d[13] + 0x5a827999), 13);
1738
1739 A = rotintlft((A + G(B, C, D) + d[2] + 0x5a827999), 3);
1740 D = rotintlft((D + G(A, B, C) + d[6] + 0x5a827999), 5);
1741 C = rotintlft((C + G(D, A, B) + d[10] + 0x5a827999), 9);
1742 B = rotintlft((B + G(C, D, A) + d[14] + 0x5a827999), 13);
1743
1744 A = rotintlft((A + G(B, C, D) + d[3] + 0x5a827999), 3);
1745 D = rotintlft((D + G(A, B, C) + d[7] + 0x5a827999), 5);
1746 C = rotintlft((C + G(D, A, B) + d[11] + 0x5a827999), 9);
1747 B = rotintlft((B + G(C, D, A) + d[15] + 0x5a827999), 13);
1748
1749 }
1750
1751 protected void round3(final int[] d) {
1752 A = rotintlft((A + H(B, C, D) + d[0] + 0x6ed9eba1), 3);
1753 D = rotintlft((D + H(A, B, C) + d[8] + 0x6ed9eba1), 9);
1754 C = rotintlft((C + H(D, A, B) + d[4] + 0x6ed9eba1), 11);
1755 B = rotintlft((B + H(C, D, A) + d[12] + 0x6ed9eba1), 15);
1756
1757 A = rotintlft((A + H(B, C, D) + d[2] + 0x6ed9eba1), 3);
1758 D = rotintlft((D + H(A, B, C) + d[10] + 0x6ed9eba1), 9);
1759 C = rotintlft((C + H(D, A, B) + d[6] + 0x6ed9eba1), 11);
1760 B = rotintlft((B + H(C, D, A) + d[14] + 0x6ed9eba1), 15);
1761
1762 A = rotintlft((A + H(B, C, D) + d[1] + 0x6ed9eba1), 3);
1763 D = rotintlft((D + H(A, B, C) + d[9] + 0x6ed9eba1), 9);
1764 C = rotintlft((C + H(D, A, B) + d[5] + 0x6ed9eba1), 11);
1765 B = rotintlft((B + H(C, D, A) + d[13] + 0x6ed9eba1), 15);
1766
1767 A = rotintlft((A + H(B, C, D) + d[3] + 0x6ed9eba1), 3);
1768 D = rotintlft((D + H(A, B, C) + d[11] + 0x6ed9eba1), 9);
1769 C = rotintlft((C + H(D, A, B) + d[7] + 0x6ed9eba1), 11);
1770 B = rotintlft((B + H(C, D, A) + d[15] + 0x6ed9eba1), 15);
1771
1772 }
1773
1774 }
1775
1776
1777
1778
1779
1780 static class HMACMD5 {
1781 protected final byte[] ipad;
1782 protected final byte[] opad;
1783 protected final MessageDigest md5;
1784
1785 HMACMD5(final byte[] input) {
1786 byte[] key = input;
1787 md5 = getMD5();
1788
1789
1790 ipad = new byte[64];
1791 opad = new byte[64];
1792
1793 int keyLength = key.length;
1794 if (keyLength > 64) {
1795
1796 md5.update(key);
1797 key = md5.digest();
1798 keyLength = key.length;
1799 }
1800 int i = 0;
1801 while (i < keyLength) {
1802 ipad[i] = (byte) (key[i] ^ (byte) 0x36);
1803 opad[i] = (byte) (key[i] ^ (byte) 0x5c);
1804 i++;
1805 }
1806 while (i < 64) {
1807 ipad[i] = (byte) 0x36;
1808 opad[i] = (byte) 0x5c;
1809 i++;
1810 }
1811
1812
1813 md5.reset();
1814 md5.update(ipad);
1815
1816 }
1817
1818
1819 byte[] getOutput() {
1820 final byte[] digest = md5.digest();
1821 md5.update(opad);
1822 return md5.digest(digest);
1823 }
1824
1825
1826 void update(final byte[] input) {
1827 md5.update(input);
1828 }
1829
1830
1831 void update(final byte[] input, final int offset, final int length) {
1832 md5.update(input, offset, length);
1833 }
1834
1835 }
1836
1837 @Override
1838 public String generateType1Msg(
1839 final String domain,
1840 final String workstation) {
1841 LOGGER.debug("generateType1Msg domain='" + domain + "' workstation='" + workstation + "'");
1842 LOGGER.debug(NTLMMessageDecoder.decode(TYPE_1_MESSAGE));
1843
1844 return TYPE_1_MESSAGE;
1845 }
1846
1847 @Override
1848 public String generateType3Msg(
1849 final String username,
1850 final String password,
1851 final String domain,
1852 final String workstation,
1853 final String challenge) throws NTLMEngineException {
1854
1855
1856 byte[] type1MessageBytes = new Type1Message().getBytes();
1857 byte[] type2MessageBytes = Base64.decodeBase64(challenge.getBytes(DEFAULT_CHARSET));
1858 if (LOGGER.isDebugEnabled()) {
1859 LOGGER.debug(NTLMMessageDecoder.decode(challenge));
1860 }
1861
1862 final DavMailNTLMEngineImpl.Type2Message t2m = new DavMailNTLMEngineImpl.Type2Message(challenge);
1863
1864 LOGGER.debug("generateType3Msg type2Flags=" + t2m.getFlags() + " target='" + t2m.getTarget() + "' username='" + username + "' domain='" + domain + "' workstation='" + workstation + "'");
1865
1866 String type3Message = getType3Message(
1867 username,
1868 password,
1869 workstation,
1870 domain,
1871 t2m.getChallenge(),
1872 t2m.getFlags(),
1873 t2m.getTarget(),
1874 t2m.getTargetInfo(),
1875 peerServerCertificate,
1876 type1MessageBytes,
1877 type2MessageBytes);
1878 if (LOGGER.isDebugEnabled()) {
1879 LOGGER.debug(NTLMMessageDecoder.decode(type3Message));
1880 }
1881 return type3Message;
1882 }
1883
1884 }