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.util.Arrays;
40 import java.util.Locale;
41
42
43
44
45
46
47 final class DavMailNTLMEngineImpl implements NTLMEngine {
48
49 static final Logger LOGGER = Logger.getLogger("davmail.http.DavMailNTLMEngineImpl");
50
51
52 private static final Charset UNICODE_LITTLE_UNMARKED = StandardCharsets.UTF_16LE;
53
54 private static final Charset DEFAULT_CHARSET = Consts.ASCII;
55
56
57
58
59
60
61 static final int FLAG_REQUEST_UNICODE_ENCODING = 0x00000001;
62 static final int FLAG_REQUEST_OEM_ENCODING = 0x00000002;
63 static final int FLAG_REQUEST_TARGET = 0x00000004;
64 static final int FLAG_REQUEST_SIGN = 0x00000010;
65 static final int FLAG_REQUEST_SEAL = 0x00000020;
66 static final int FLAG_REQUEST_LAN_MANAGER_KEY = 0x00000080;
67 static final int FLAG_REQUEST_NTLMv1 = 0x00000200;
68 static final int FLAG_DOMAIN_PRESENT = 0x00001000;
69 static final int FLAG_WORKSTATION_PRESENT = 0x00002000;
70 static final int FLAG_REQUEST_ALWAYS_SIGN = 0x00008000;
71 static final int FLAG_REQUEST_NTLM2_SESSION = 0x00080000;
72 static final int FLAG_REQUEST_VERSION = 0x02000000;
73 static final int FLAG_TARGETINFO_PRESENT = 0x00800000;
74 static final int FLAG_REQUEST_128BIT_KEY_EXCH = 0x20000000;
75 static final int FLAG_REQUEST_EXPLICIT_KEY_EXCH = 0x40000000;
76 static final int FLAG_REQUEST_56BIT_ENCRYPTION = 0x80000000;
77
78
79
80 static final int MSV_AV_EOL = 0x0000;
81 static final int MSV_AV_NB_COMPUTER_NAME = 0x0001;
82 static final int MSV_AV_NB_DOMAIN_NAME = 0x0002;
83 static final int MSV_AV_DNS_COMPUTER_NAME = 0x0003;
84 static final int MSV_AV_DNS_DOMAIN_NAME = 0x0004;
85 static final int MSV_AV_DNS_TREE_NAME = 0x0005;
86 static final int MSV_AV_FLAGS = 0x0006;
87 static final int MSV_AV_TIMESTAMP = 0x0007;
88 static final int MSV_AV_SINGLE_HOST = 0x0008;
89 static final int MSV_AV_TARGET_NAME = 0x0009;
90 static final int MSV_AV_CHANNEL_BINDINGS = 0x000A;
91
92 static final int MSV_AV_FLAGS_ACCOUNT_AUTH_CONSTAINED = 0x00000001;
93 static final int MSV_AV_FLAGS_MIC = 0x00000002;
94 static final int MSV_AV_FLAGS_UNTRUSTED_TARGET_SPN = 0x00000004;
95
96 private static final SecureRandom RNG;
97
98 static {
99 try {
100 RNG = java.security.SecureRandom.getInstance("SHA1PRNG");
101 } catch (final NoSuchAlgorithmException e) {
102 throw new RuntimeException(e.getMessage(), e);
103 }
104 }
105
106
107 private static final byte[] SIGNATURE = getNullTerminatedAsciiString("NTLMSSP");
108
109
110 private static final byte[] MAGIC_TLS_SERVER_ENDPOINT = "tls-server-end-point:".getBytes(Consts.ASCII);
111
112 private static byte[] getNullTerminatedAsciiString(final String source) {
113 final byte[] bytesWithoutNull = source.getBytes(Consts.ASCII);
114 final byte[] target = new byte[bytesWithoutNull.length + 1];
115 System.arraycopy(bytesWithoutNull, 0, target, 0, bytesWithoutNull.length);
116 target[bytesWithoutNull.length] = (byte) 0x00;
117 return target;
118 }
119
120 private static final String TYPE_1_MESSAGE = new DavMailNTLMEngineImpl.Type1Message().getResponse();
121
122 Certificate peerServerCertificate = null;
123
124 DavMailNTLMEngineImpl() {
125 }
126
127
128
129
130
131 public void setPeerServerCertificate(Certificate peerServerCertificate) {
132 this.peerServerCertificate = peerServerCertificate;
133 }
134
135
136
137
138
139
140
141
142
143
144
145
146 static String getType1Message(final String host, final String domain) {
147
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 } else {
1304 computeMic = false;
1305 }
1306
1307
1308 final DavMailNTLMEngineImpl.CipherGen gen = new DavMailNTLMEngineImpl.CipherGen(currentTime,
1309 unqualifiedDomain,
1310 user,
1311 password,
1312 nonce,
1313 target,
1314 responseTargetInformation);
1315
1316
1317
1318 byte[] userSessionKey;
1319 try {
1320
1321
1322 if (((type2Flags & FLAG_TARGETINFO_PRESENT) != 0) &&
1323 targetInformation != null && target != null) {
1324
1325 ntResp = gen.getNTLMv2Response();
1326 lmResp = gen.getLMv2Response();
1327 if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) {
1328 userSessionKey = gen.getLanManagerSessionKey();
1329 } else {
1330 userSessionKey = gen.getNTLMv2UserSessionKey();
1331 }
1332 } else {
1333
1334 if ((type2Flags & FLAG_REQUEST_NTLM2_SESSION) != 0) {
1335
1336 ntResp = gen.getNTLM2SessionResponse();
1337 lmResp = gen.getLM2SessionResponse();
1338 if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) {
1339 userSessionKey = gen.getLanManagerSessionKey();
1340 } else {
1341 userSessionKey = gen.getNTLM2SessionResponseUserSessionKey();
1342 }
1343 } else {
1344 ntResp = gen.getNTLMResponse();
1345 lmResp = gen.getLMResponse();
1346 if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) {
1347 userSessionKey = gen.getLanManagerSessionKey();
1348 } else {
1349 userSessionKey = gen.getNTLMUserSessionKey();
1350 }
1351 }
1352 }
1353 } catch (final NTLMEngineException e) {
1354
1355
1356 ntResp = new byte[0];
1357 lmResp = gen.getLMResponse();
1358 if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) {
1359 userSessionKey = gen.getLanManagerSessionKey();
1360 } else {
1361 userSessionKey = gen.getLMUserSessionKey();
1362 }
1363 }
1364
1365 if ((type2Flags & FLAG_REQUEST_SIGN) != 0) {
1366 if ((type2Flags & FLAG_REQUEST_EXPLICIT_KEY_EXCH) != 0) {
1367 exportedSessionKey = gen.getSecondaryKey();
1368 sessionKey = RC4(exportedSessionKey, userSessionKey);
1369 } else {
1370 sessionKey = userSessionKey;
1371 exportedSessionKey = sessionKey;
1372 }
1373 } else {
1374 if (computeMic) {
1375 throw new NTLMEngineException("Cannot sign/seal: no exported session key");
1376 }
1377 sessionKey = null;
1378 exportedSessionKey = null;
1379 }
1380 final Charset charset = getCharset(type2Flags);
1381 hostBytes = unqualifiedHost != null ? unqualifiedHost.getBytes(charset) : null;
1382 domainBytes = unqualifiedDomain != null ? unqualifiedDomain
1383 .toUpperCase(Locale.ROOT).getBytes(charset) : null;
1384 userBytes = user.getBytes(charset);
1385 }
1386
1387 public byte[] getEncryptedRandomSessionKey() {
1388 return sessionKey;
1389 }
1390
1391 public byte[] getExportedSessionKey() {
1392 return exportedSessionKey;
1393 }
1394
1395
1396 @Override
1397 protected void buildMessage() {
1398 final int ntRespLen = ntResp.length;
1399 final int lmRespLen = lmResp.length;
1400
1401 final int domainLen = domainBytes != null ? domainBytes.length : 0;
1402 final int hostLen = hostBytes != null ? hostBytes.length : 0;
1403 final int userLen = userBytes.length;
1404 final int sessionKeyLen;
1405 if (sessionKey != null) {
1406 sessionKeyLen = sessionKey.length;
1407 } else {
1408 sessionKeyLen = 0;
1409 }
1410
1411
1412 final int lmRespOffset = 72 +
1413 (computeMic ? 16 : 0);
1414 final int ntRespOffset = lmRespOffset + lmRespLen;
1415 final int domainOffset = ntRespOffset + ntRespLen;
1416 final int userOffset = domainOffset + domainLen;
1417 final int hostOffset = userOffset + userLen;
1418 final int sessionKeyOffset = hostOffset + hostLen;
1419 final int finalLength = sessionKeyOffset + sessionKeyLen;
1420
1421
1422 prepareResponse(finalLength, 3);
1423
1424
1425 addUShort(lmRespLen);
1426 addUShort(lmRespLen);
1427
1428
1429 addULong(lmRespOffset);
1430
1431
1432 addUShort(ntRespLen);
1433 addUShort(ntRespLen);
1434
1435
1436 addULong(ntRespOffset);
1437
1438
1439 addUShort(domainLen);
1440 addUShort(domainLen);
1441
1442
1443 addULong(domainOffset);
1444
1445
1446 addUShort(userLen);
1447 addUShort(userLen);
1448
1449
1450 addULong(userOffset);
1451
1452
1453 addUShort(hostLen);
1454 addUShort(hostLen);
1455
1456
1457 addULong(hostOffset);
1458
1459
1460 addUShort(sessionKeyLen);
1461 addUShort(sessionKeyLen);
1462
1463
1464 addULong(sessionKeyOffset);
1465
1466
1467 addULong(
1468
1469
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 type2Flags
1495 );
1496
1497
1498 addUShort(0x0105);
1499
1500 addULong(2600);
1501
1502 addUShort(0x0f00);
1503
1504 int micPosition = -1;
1505 if (computeMic) {
1506 micPosition = currentOutputPosition;
1507 currentOutputPosition += 16;
1508 }
1509
1510
1511 addBytes(lmResp);
1512 addBytes(ntResp);
1513 addBytes(domainBytes);
1514 addBytes(userBytes);
1515 addBytes(hostBytes);
1516 if (sessionKey != null) {
1517 addBytes(sessionKey);
1518 }
1519
1520
1521
1522 if (computeMic) {
1523
1524 final DavMailNTLMEngineImpl.HMACMD5 hmacMD5 = new HMACMD5(exportedSessionKey);
1525 hmacMD5.update(type1Message);
1526 hmacMD5.update(type2Message);
1527 hmacMD5.update(messageContents);
1528 final byte[] mic = hmacMD5.getOutput();
1529 System.arraycopy(mic, 0, messageContents, micPosition, mic.length);
1530 }
1531 }
1532
1533
1534
1535
1536
1537 private byte[] addGssMicAvsToTargetInfo(final byte[] originalTargetInfo,
1538 final Certificate peerServerCertificate) throws NTLMEngineException {
1539 final byte[] newTargetInfo = new byte[originalTargetInfo.length + 8 + 20];
1540 final int appendLength = originalTargetInfo.length - 4;
1541 System.arraycopy(originalTargetInfo, 0, newTargetInfo, 0, appendLength);
1542 writeUShort(newTargetInfo, MSV_AV_FLAGS, appendLength);
1543 writeUShort(newTargetInfo, 4, appendLength + 2);
1544 writeULong(newTargetInfo, MSV_AV_FLAGS_MIC, appendLength + 4);
1545 writeUShort(newTargetInfo, MSV_AV_CHANNEL_BINDINGS, appendLength + 8);
1546 writeUShort(newTargetInfo, 16, appendLength + 10);
1547
1548 final byte[] channelBindingsHash;
1549 try {
1550 final byte[] certBytes = peerServerCertificate.getEncoded();
1551 final MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
1552 final byte[] certHashBytes = sha256.digest(certBytes);
1553 final byte[] channelBindingStruct = new byte[16 + 4 + MAGIC_TLS_SERVER_ENDPOINT.length
1554 + certHashBytes.length];
1555 writeULong(channelBindingStruct, 0x00000035, 16);
1556 System.arraycopy(MAGIC_TLS_SERVER_ENDPOINT, 0, channelBindingStruct, 20,
1557 MAGIC_TLS_SERVER_ENDPOINT.length);
1558 System.arraycopy(certHashBytes, 0, channelBindingStruct, 20 + MAGIC_TLS_SERVER_ENDPOINT.length,
1559 certHashBytes.length);
1560 final MessageDigest md5 = getMD5();
1561 channelBindingsHash = md5.digest(channelBindingStruct);
1562 } catch (CertificateEncodingException | NoSuchAlgorithmException e) {
1563 throw new NTLMEngineException(e.getMessage(), e);
1564 }
1565
1566 System.arraycopy(channelBindingsHash, 0, newTargetInfo, appendLength + 12, 16);
1567 return newTargetInfo;
1568 }
1569
1570 }
1571
1572 static void writeUShort(final byte[] buffer, final int value, final int offset) {
1573 buffer[offset] = (byte) (value & 0xff);
1574 buffer[offset + 1] = (byte) (value >> 8 & 0xff);
1575 }
1576
1577 static void writeULong(final byte[] buffer, final int value, final int offset) {
1578 buffer[offset] = (byte) (value & 0xff);
1579 buffer[offset + 1] = (byte) (value >> 8 & 0xff);
1580 buffer[offset + 2] = (byte) (value >> 16 & 0xff);
1581 buffer[offset + 3] = (byte) (value >> 24 & 0xff);
1582 }
1583
1584 static int F(final int x, final int y, final int z) {
1585 return ((x & y) | (~x & z));
1586 }
1587
1588 static int G(final int x, final int y, final int z) {
1589 return ((x & y) | (x & z) | (y & z));
1590 }
1591
1592 static int H(final int x, final int y, final int z) {
1593 return (x ^ y ^ z);
1594 }
1595
1596 static int rotintlft(final int val, final int numbits) {
1597 return ((val << numbits) | (val >>> (32 - numbits)));
1598 }
1599
1600 static MessageDigest getMD5() {
1601 try {
1602 return MessageDigest.getInstance("MD5");
1603 } catch (final NoSuchAlgorithmException ex) {
1604 throw new RuntimeException("MD5 message digest doesn't seem to exist - fatal error: " + ex.getMessage(), ex);
1605 }
1606 }
1607
1608
1609
1610
1611
1612
1613
1614
1615 static class MD4 {
1616 protected int A = 0x67452301;
1617 protected int B = 0xefcdab89;
1618 protected int C = 0x98badcfe;
1619 protected int D = 0x10325476;
1620 protected long count = 0L;
1621 protected final byte[] dataBuffer = new byte[64];
1622
1623 MD4() {
1624 }
1625
1626 void update(final byte[] input) {
1627
1628
1629
1630 int curBufferPos = (int) (count & 63L);
1631 int inputIndex = 0;
1632 while (input.length - inputIndex + curBufferPos >= dataBuffer.length) {
1633
1634
1635
1636 final int transferAmt = dataBuffer.length - curBufferPos;
1637 System.arraycopy(input, inputIndex, dataBuffer, curBufferPos, transferAmt);
1638 count += transferAmt;
1639 curBufferPos = 0;
1640 inputIndex += transferAmt;
1641 processBuffer();
1642 }
1643
1644
1645
1646 if (inputIndex < input.length) {
1647 final int transferAmt = input.length - inputIndex;
1648 System.arraycopy(input, inputIndex, dataBuffer, curBufferPos, transferAmt);
1649 count += transferAmt;
1650 }
1651 }
1652
1653 byte[] getOutput() {
1654
1655
1656 final int bufferIndex = (int) (count & 63L);
1657 final int padLen = (bufferIndex < 56) ? (56 - bufferIndex) : (120 - bufferIndex);
1658 final byte[] postBytes = new byte[padLen + 8];
1659
1660
1661 postBytes[0] = (byte) 0x80;
1662
1663 for (int i = 0; i < 8; i++) {
1664 postBytes[padLen + i] = (byte) ((count * 8) >>> (8 * i));
1665 }
1666
1667
1668 update(postBytes);
1669
1670
1671 final byte[] result = new byte[16];
1672 writeULong(result, A, 0);
1673 writeULong(result, B, 4);
1674 writeULong(result, C, 8);
1675 writeULong(result, D, 12);
1676 return result;
1677 }
1678
1679 protected void processBuffer() {
1680
1681 final int[] d = new int[16];
1682
1683 for (int i = 0; i < 16; i++) {
1684 d[i] = (dataBuffer[i * 4] & 0xff) + ((dataBuffer[i * 4 + 1] & 0xff) << 8)
1685 + ((dataBuffer[i * 4 + 2] & 0xff) << 16)
1686 + ((dataBuffer[i * 4 + 3] & 0xff) << 24);
1687 }
1688
1689
1690 final int AA = A;
1691 final int BB = B;
1692 final int CC = C;
1693 final int DD = D;
1694 round1(d);
1695 round2(d);
1696 round3(d);
1697 A += AA;
1698 B += BB;
1699 C += CC;
1700 D += DD;
1701
1702 }
1703
1704 protected void round1(final int[] d) {
1705 A = rotintlft((A + F(B, C, D) + d[0]), 3);
1706 D = rotintlft((D + F(A, B, C) + d[1]), 7);
1707 C = rotintlft((C + F(D, A, B) + d[2]), 11);
1708 B = rotintlft((B + F(C, D, A) + d[3]), 19);
1709
1710 A = rotintlft((A + F(B, C, D) + d[4]), 3);
1711 D = rotintlft((D + F(A, B, C) + d[5]), 7);
1712 C = rotintlft((C + F(D, A, B) + d[6]), 11);
1713 B = rotintlft((B + F(C, D, A) + d[7]), 19);
1714
1715 A = rotintlft((A + F(B, C, D) + d[8]), 3);
1716 D = rotintlft((D + F(A, B, C) + d[9]), 7);
1717 C = rotintlft((C + F(D, A, B) + d[10]), 11);
1718 B = rotintlft((B + F(C, D, A) + d[11]), 19);
1719
1720 A = rotintlft((A + F(B, C, D) + d[12]), 3);
1721 D = rotintlft((D + F(A, B, C) + d[13]), 7);
1722 C = rotintlft((C + F(D, A, B) + d[14]), 11);
1723 B = rotintlft((B + F(C, D, A) + d[15]), 19);
1724 }
1725
1726 protected void round2(final int[] d) {
1727 A = rotintlft((A + G(B, C, D) + d[0] + 0x5a827999), 3);
1728 D = rotintlft((D + G(A, B, C) + d[4] + 0x5a827999), 5);
1729 C = rotintlft((C + G(D, A, B) + d[8] + 0x5a827999), 9);
1730 B = rotintlft((B + G(C, D, A) + d[12] + 0x5a827999), 13);
1731
1732 A = rotintlft((A + G(B, C, D) + d[1] + 0x5a827999), 3);
1733 D = rotintlft((D + G(A, B, C) + d[5] + 0x5a827999), 5);
1734 C = rotintlft((C + G(D, A, B) + d[9] + 0x5a827999), 9);
1735 B = rotintlft((B + G(C, D, A) + d[13] + 0x5a827999), 13);
1736
1737 A = rotintlft((A + G(B, C, D) + d[2] + 0x5a827999), 3);
1738 D = rotintlft((D + G(A, B, C) + d[6] + 0x5a827999), 5);
1739 C = rotintlft((C + G(D, A, B) + d[10] + 0x5a827999), 9);
1740 B = rotintlft((B + G(C, D, A) + d[14] + 0x5a827999), 13);
1741
1742 A = rotintlft((A + G(B, C, D) + d[3] + 0x5a827999), 3);
1743 D = rotintlft((D + G(A, B, C) + d[7] + 0x5a827999), 5);
1744 C = rotintlft((C + G(D, A, B) + d[11] + 0x5a827999), 9);
1745 B = rotintlft((B + G(C, D, A) + d[15] + 0x5a827999), 13);
1746
1747 }
1748
1749 protected void round3(final int[] d) {
1750 A = rotintlft((A + H(B, C, D) + d[0] + 0x6ed9eba1), 3);
1751 D = rotintlft((D + H(A, B, C) + d[8] + 0x6ed9eba1), 9);
1752 C = rotintlft((C + H(D, A, B) + d[4] + 0x6ed9eba1), 11);
1753 B = rotintlft((B + H(C, D, A) + d[12] + 0x6ed9eba1), 15);
1754
1755 A = rotintlft((A + H(B, C, D) + d[2] + 0x6ed9eba1), 3);
1756 D = rotintlft((D + H(A, B, C) + d[10] + 0x6ed9eba1), 9);
1757 C = rotintlft((C + H(D, A, B) + d[6] + 0x6ed9eba1), 11);
1758 B = rotintlft((B + H(C, D, A) + d[14] + 0x6ed9eba1), 15);
1759
1760 A = rotintlft((A + H(B, C, D) + d[1] + 0x6ed9eba1), 3);
1761 D = rotintlft((D + H(A, B, C) + d[9] + 0x6ed9eba1), 9);
1762 C = rotintlft((C + H(D, A, B) + d[5] + 0x6ed9eba1), 11);
1763 B = rotintlft((B + H(C, D, A) + d[13] + 0x6ed9eba1), 15);
1764
1765 A = rotintlft((A + H(B, C, D) + d[3] + 0x6ed9eba1), 3);
1766 D = rotintlft((D + H(A, B, C) + d[11] + 0x6ed9eba1), 9);
1767 C = rotintlft((C + H(D, A, B) + d[7] + 0x6ed9eba1), 11);
1768 B = rotintlft((B + H(C, D, A) + d[15] + 0x6ed9eba1), 15);
1769
1770 }
1771
1772 }
1773
1774
1775
1776
1777
1778 static class HMACMD5 {
1779 protected final byte[] ipad;
1780 protected final byte[] opad;
1781 protected final MessageDigest md5;
1782
1783 HMACMD5(final byte[] input) {
1784 byte[] key = input;
1785 md5 = getMD5();
1786
1787
1788 ipad = new byte[64];
1789 opad = new byte[64];
1790
1791 int keyLength = key.length;
1792 if (keyLength > 64) {
1793
1794 md5.update(key);
1795 key = md5.digest();
1796 keyLength = key.length;
1797 }
1798 int i = 0;
1799 while (i < keyLength) {
1800 ipad[i] = (byte) (key[i] ^ (byte) 0x36);
1801 opad[i] = (byte) (key[i] ^ (byte) 0x5c);
1802 i++;
1803 }
1804 while (i < 64) {
1805 ipad[i] = (byte) 0x36;
1806 opad[i] = (byte) 0x5c;
1807 i++;
1808 }
1809
1810
1811 md5.reset();
1812 md5.update(ipad);
1813
1814 }
1815
1816
1817 byte[] getOutput() {
1818 final byte[] digest = md5.digest();
1819 md5.update(opad);
1820 return md5.digest(digest);
1821 }
1822
1823
1824 void update(final byte[] input) {
1825 md5.update(input);
1826 }
1827
1828
1829 void update(final byte[] input, final int offset, final int length) {
1830 md5.update(input, offset, length);
1831 }
1832
1833 }
1834
1835 @Override
1836 public String generateType1Msg(
1837 final String domain,
1838 final String workstation) {
1839 LOGGER.debug("generateType1Msg domain='" + domain + "' workstation='" + workstation + "'");
1840 return getType1Message(workstation, domain);
1841 }
1842
1843 @Override
1844 public String generateType3Msg(
1845 final String username,
1846 final String password,
1847 final String domain,
1848 final String workstation,
1849 final String challenge) throws NTLMEngineException {
1850
1851
1852 byte[] type1MessageBytes = new Type1Message().getBytes();
1853 byte[] type2MessageBytes = Base64.decodeBase64(challenge.getBytes(DEFAULT_CHARSET));
1854
1855 final DavMailNTLMEngineImpl.Type2Message t2m = new DavMailNTLMEngineImpl.Type2Message(challenge);
1856
1857 LOGGER.debug("generateType3Msg type2Flags " + t2m.getFlags() + " target='" + t2m.getTarget() + " username='" + username + "'");
1858
1859 return getType3Message(
1860 username,
1861 password,
1862 workstation,
1863 domain,
1864 t2m.getChallenge(),
1865 t2m.getFlags(),
1866 t2m.getTarget(),
1867 t2m.getTargetInfo(),
1868 peerServerCertificate,
1869 type1MessageBytes,
1870 type2MessageBytes);
1871 }
1872
1873 }