View Javadoc
1   /*
2    * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
3    * Copyright (C) 2010  Mickael Guessant
4    *
5    * This program is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU General Public License
7    * as published by the Free Software Foundation; either version 2
8    * of the License, or (at your option) any later version.
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with this program; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
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   * Provides an implementation for NTLMv1, NTLMv2, and NTLM2 Session forms of the NTLM
44   * authentication protocol.
45   * Duplicate code from NTLMEngineImpl to implement channel binding
46   */
47  final class DavMailNTLMEngineImpl implements NTLMEngine {
48  
49      static final Logger LOGGER = Logger.getLogger("davmail.http.DavMailNTLMEngineImpl");
50  
51      /** Unicode encoding */
52      private static final Charset UNICODE_LITTLE_UNMARKED = StandardCharsets.UTF_16LE;
53      /** Character encoding */
54      private static final Charset DEFAULT_CHARSET = Consts.ASCII;
55  
56      // Flags we use; descriptions according to:
57      // http://davenport.sourceforge.net/ntlm.html
58      // and
59      // http://msdn.microsoft.com/en-us/library/cc236650%28v=prot.20%29.aspx
60      // [MS-NLMP] section 2.2.2.5
61      static final int FLAG_REQUEST_UNICODE_ENCODING = 0x00000001;      // Unicode string encoding requested
62      static final int FLAG_REQUEST_OEM_ENCODING = 0x00000002;      // OEM string encoding requested
63      static final int FLAG_REQUEST_TARGET = 0x00000004;                      // Requests target field
64      static final int FLAG_REQUEST_SIGN = 0x00000010;  // Requests all messages have a signature attached, in NEGOTIATE message.
65      static final int FLAG_REQUEST_SEAL = 0x00000020;  // Request key exchange for message confidentiality in NEGOTIATE message.  MUST be used in conjunction with 56BIT.
66      static final int FLAG_REQUEST_LAN_MANAGER_KEY = 0x00000080;    // Request Lan Manager key instead of user session key
67      static final int FLAG_REQUEST_NTLMv1 = 0x00000200; // Request NTLMv1 security.  MUST be set in NEGOTIATE and CHALLENGE both
68      static final int FLAG_DOMAIN_PRESENT = 0x00001000;        // Domain is present in message
69      static final int FLAG_WORKSTATION_PRESENT = 0x00002000;   // Workstation is present in message
70      static final int FLAG_REQUEST_ALWAYS_SIGN = 0x00008000;   // Requests a signature block on all messages.  Overridden by REQUEST_SIGN and REQUEST_SEAL.
71      static final int FLAG_REQUEST_NTLM2_SESSION = 0x00080000; // From server in challenge, requesting NTLM2 session security
72      static final int FLAG_REQUEST_VERSION = 0x02000000;       // Request protocol version
73      static final int FLAG_TARGETINFO_PRESENT = 0x00800000;    // From server in challenge message, indicating targetinfo is present
74      static final int FLAG_REQUEST_128BIT_KEY_EXCH = 0x20000000; // Request explicit 128-bit key exchange
75      static final int FLAG_REQUEST_EXPLICIT_KEY_EXCH = 0x40000000;     // Request explicit key exchange
76      static final int FLAG_REQUEST_56BIT_ENCRYPTION = 0x80000000;      // Must be used in conjunction with SEAL
77  
78      // Attribute-value identifiers (AvId)
79      // according to [MS-NLMP] section 2.2.2.1
80      static final int MSV_AV_EOL = 0x0000; // Indicates that this is the last AV_PAIR in the list.
81      static final int MSV_AV_NB_COMPUTER_NAME = 0x0001; // The server's NetBIOS computer name.
82      static final int MSV_AV_NB_DOMAIN_NAME = 0x0002; // The server's NetBIOS domain name.
83      static final int MSV_AV_DNS_COMPUTER_NAME = 0x0003; // The fully qualified domain name (FQDN) of the computer.
84      static final int MSV_AV_DNS_DOMAIN_NAME = 0x0004; // The FQDN of the domain.
85      static final int MSV_AV_DNS_TREE_NAME = 0x0005; // The FQDN of the forest.
86      static final int MSV_AV_FLAGS = 0x0006; // A 32-bit value indicating server or client configuration.
87      static final int MSV_AV_TIMESTAMP = 0x0007; // server local time
88      static final int MSV_AV_SINGLE_HOST = 0x0008; // A Single_Host_Data structure.
89      static final int MSV_AV_TARGET_NAME = 0x0009; // The SPN of the target server.
90      static final int MSV_AV_CHANNEL_BINDINGS = 0x000A; // A channel bindings hash.
91  
92      static final int MSV_AV_FLAGS_ACCOUNT_AUTH_CONSTAINED = 0x00000001; // Indicates to the client that the account authentication is constrained.
93      static final int MSV_AV_FLAGS_MIC = 0x00000002; // Indicates that the client is providing message integrity in the MIC field in the AUTHENTICATE_MESSAGE.
94      static final int MSV_AV_FLAGS_UNTRUSTED_TARGET_SPN = 0x00000004; // Indicates that the client is providing a target SPN generated from an untrusted source.
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     /** The signature string as bytes in the default encoding */
107     private static final byte[] SIGNATURE = getNullTerminatedAsciiString("NTLMSSP");
108 
109     // prefix for GSS API channel binding
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      * Store connection certificate for channel binding implementation.
129      * @param peerServerCertificate certificate
130      */
131     public void setPeerServerCertificate(Certificate peerServerCertificate) {
132         this.peerServerCertificate = peerServerCertificate;
133     }
134 
135     /**
136      * Creates the first message (type 1 message) in the NTLM authentication
137      * sequence. This message includes the user name, domain and host for the
138      * authentication session.
139      *
140      * @param host
141      *            the computer name of the host requesting authentication.
142      * @param domain
143      *            The domain to authenticate with.
144      * @return String the message to add to the HTTP request header.
145      */
146     static String getType1Message(final String host, final String domain) {
147         // For compatibility reason do not include domain and host in type 1 message
148         //return new Type1Message(domain, host).getResponse();
149         return TYPE_1_MESSAGE;
150     }
151 
152     /**
153      * Creates the type 3 message using the given server nonce. The type 3
154      * message includes all the information for authentication, host, domain,
155      * username and the result of encrypting the nonce sent by the server using
156      * the user's password as the key.
157      *
158      * @param user
159      *            The user name. This should not include the domain name.
160      * @param password
161      *            The password.
162      * @param host
163      *            The host that is originating the authentication request.
164      * @param domain
165      *            The domain to authenticate within.
166      * @param nonce
167      *            the 8 byte array the server sent.
168      * @return The type 3 message.
169      * @throws NTLMEngineException
170      *             If {link #Type3Message(String, String, String, String, byte[], int, String, byte[])} fails.
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      * Creates the type 3 message using the given server nonce. The type 3
181      * message includes all the information for authentication, host, domain,
182      * username and the result of encrypting the nonce sent by the server using
183      * the user's password as the key.
184      *
185      * @param user
186      *            The user name. This should not include the domain name.
187      * @param password
188      *            The password.
189      * @param host
190      *            The host that is originating the authentication request.
191      * @param domain
192      *            The domain to authenticate within.
193      * @param nonce
194      *            the 8 byte array the server sent.
195      * @return The type 3 message.
196      * @throws NTLMEngineException
197      *             If {link #Type3Message(String, String, String, String, byte[], int, String, byte[], Certificate, byte[], byte[])} fails.
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     /** Calculate a challenge block */
234     private static byte[] makeRandomChallenge() {
235         final byte[] rval = new byte[8];
236         RNG.nextBytes(rval);
237         return rval;
238     }
239 
240     /** Calculate a 16-byte secondary key */
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         // Information we can generate but may be passed in (for testing)
259         protected byte[] clientChallenge;
260         protected byte[] clientChallenge2;
261         protected byte[] secondaryKey;
262         protected byte[] timestamp;
263 
264         // Stuff we always generate
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         /** Calculate and return client challenge */
312         public byte[] getClientChallenge() {
313             if (clientChallenge == null) {
314                 clientChallenge = makeRandomChallenge();
315             }
316             return clientChallenge;
317         }
318 
319         /** Calculate and return second client challenge */
320         public byte[] getClientChallenge2() {
321             if (clientChallenge2 == null) {
322                 clientChallenge2 = makeRandomChallenge();
323             }
324             return clientChallenge2;
325         }
326 
327         /** Calculate and return random secondary key */
328         public byte[] getSecondaryKey() {
329             if (secondaryKey == null) {
330                 secondaryKey = makeSecondaryKey();
331             }
332             return secondaryKey;
333         }
334 
335         /** Calculate and return the LMHash */
336         public byte[] getLMHash()
337                 throws NTLMEngineException {
338             if (lmHash == null) {
339                 lmHash = lmHash(password);
340             }
341             return lmHash;
342         }
343 
344         /** Calculate and return the LMResponse */
345         public byte[] getLMResponse()
346                 throws NTLMEngineException {
347             if (lmResponse == null) {
348                 lmResponse = lmResponse(getLMHash(), challenge);
349             }
350             return lmResponse;
351         }
352 
353         /** Calculate and return the NTLMHash */
354         public byte[] getNTLMHash() {
355             if (ntlmHash == null) {
356                 ntlmHash = ntlmHash(password);
357             }
358             return ntlmHash;
359         }
360 
361         /** Calculate and return the NTLMResponse */
362         public byte[] getNTLMResponse()
363                 throws NTLMEngineException {
364             if (ntlmResponse == null) {
365                 ntlmResponse = lmResponse(getNTLMHash(), challenge);
366             }
367             return ntlmResponse;
368         }
369 
370         /** Calculate the LMv2 hash */
371         public byte[] getLMv2Hash()
372                 throws NTLMEngineException {
373             if (lmv2Hash == null) {
374                 lmv2Hash = lmv2Hash(domain, user, getNTLMHash());
375             }
376             return lmv2Hash;
377         }
378 
379         /** Calculate the NTLMv2 hash */
380         public byte[] getNTLMv2Hash()
381                 throws NTLMEngineException {
382             if (ntlmv2Hash == null) {
383                 ntlmv2Hash = ntlmv2Hash(domain, user, getNTLMHash());
384             }
385             return ntlmv2Hash;
386         }
387 
388         /** Calculate a timestamp */
389         public byte[] getTimestamp() {
390             if (timestamp == null) {
391                 long time = this.currentTime;
392                 time += 11644473600000L; // milliseconds from January 1, 1601 -> epoch.
393                 time *= 10000; // tenths of a microsecond.
394                 // convert to little-endian byte array.
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         /** Calculate the NTLMv2Blob */
405         public byte[] getNTLMv2Blob() {
406             if (ntlmv2Blob == null) {
407                 ntlmv2Blob = createBlob(getClientChallenge2(), targetInformation, getTimestamp());
408             }
409             return ntlmv2Blob;
410         }
411 
412         /** Calculate the NTLMv2Response */
413         public byte[] getNTLMv2Response()
414                 throws NTLMEngineException {
415             if (ntlmv2Response == null) {
416                 ntlmv2Response = lmv2Response(getNTLMv2Hash(), challenge, getNTLMv2Blob());
417             }
418             return ntlmv2Response;
419         }
420 
421         /** Calculate the LMv2Response */
422         public byte[] getLMv2Response()
423                 throws NTLMEngineException {
424             if (lmv2Response == null) {
425                 lmv2Response = lmv2Response(getLMv2Hash(), challenge, getClientChallenge());
426             }
427             return lmv2Response;
428         }
429 
430         /** Get NTLM2SessionResponse */
431         public byte[] getNTLM2SessionResponse()
432                 throws NTLMEngineException {
433             if (ntlm2SessionResponse == null) {
434                 ntlm2SessionResponse = ntlm2SessionResponse(getNTLMHash(), challenge, getClientChallenge());
435             }
436             return ntlm2SessionResponse;
437         }
438 
439         /** Calculate and return LM2 session response */
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         /** Get LMUserSessionKey */
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         /** Get NTLMUserSessionKey */
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         /** GetNTLMv2UserSessionKey */
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         /** Get NTLM2SessionResponseUserSessionKey */
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         /** Get LAN Manager session key */
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     /** Calculates HMAC-MD5 */
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     /** Calculates RC4 */
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      * Calculates the NTLM2 Session Response for the given challenge, using the
547      * specified password and client challenge.
548      *
549      * @return The NTLM2 Session Response. This is placed in the NTLM response
550      *         field of the Type 3 message; the LM response field contains the
551      *         client challenge, null-padded to 24 bytes.
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      * Creates the LM Hash of the user's password.
573      *
574      * @param password
575      *            The password.
576      *
577      * @return The LM Hash of the given password, used in the calculation of the
578      *         LM Response.
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      * Creates the NTLM Hash of the user's password.
605      *
606      * @param password
607      *            The password.
608      *
609      * @return The NTLM Hash of the given password, used in the calculation of
610      *         the NTLM Response and the NTLMv2 and LMv2 Hashes.
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      * Creates the LMv2 Hash of the user's password.
621      *
622      * @return The LMv2 Hash, used in the calculation of the NTLMv2 and LMv2
623      *         Responses.
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         // Upper case username, upper case domain!
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      * Creates the NTLMv2 Hash of the user's password.
638      *
639      * @return The NTLMv2 Hash, used in the calculation of the NTLMv2 and LMv2
640      *         Responses.
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         // Upper case username, mixed case target!!
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      * Creates the LM Response from the given hash and Type 2 challenge.
654      *
655      * @param hash
656      *            The LM or NTLM Hash.
657      * @param challenge
658      *            The server challenge from the Type 2 message.
659      *
660      * @return The response (either LM or NTLM, depending on the provided hash).
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      * Creates the LMv2 Response from the given hash, client data, and Type 2
688      * challenge.
689      *
690      * @param hash
691      *            The NTLMv2 Hash.
692      * @param clientData
693      *            The client data (blob or client challenge).
694      * @param challenge
695      *            The server challenge from the Type 2 message.
696      *
697      * @return The response (either NTLMv2 or LMv2, depending on the client
698      *         data).
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      * Creates the NTLMv2 blob from the given target information block and
726      * client challenge.
727      *
728      * @param targetInformation
729      *            The target information block from the Type 2 message.
730      * @param clientChallenge
731      *            The random 8-byte client challenge.
732      *
733      * @return The blob, used in the calculation of the NTLMv2 Response.
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      * Creates a DES encryption key from the given key material.
762      *
763      * @param bytes
764      *            A byte array containing the DES key material.
765      * @param offset
766      *            The offset in the given byte array at which the 7-byte key
767      *            material starts.
768      *
769      * @return A DES encryption key created from the key material starting at
770      *         the specified offset in the given byte array.
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      * Applies odd parity to the given byte array.
790      *
791      * @param bytes
792      *            The data whose parity bits are to be adjusted for odd parity.
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      * Find the character set based on the flags.
809      * @param flags is the flags.
810      * @return the character set.
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     /** Strip dot suffix from a name */
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     /** Convert host to standard form */
832     private static String convertHost(final String host) {
833         return stripDotSuffix(host);
834     }
835 
836     /** Convert domain to standard form */
837     private static String convertDomain(final String domain) {
838         return stripDotSuffix(domain);
839     }
840 
841     /** NTLM message generation, base class */
842     static class NTLMMessage {
843         /** The current response */
844         protected byte[] messageContents = null;
845 
846         /** The current output position */
847         protected int currentOutputPosition = 0;
848 
849         /** Constructor to use when message contents are not yet known */
850         NTLMMessage() {
851         }
852 
853         /** Constructor taking a string */
854         NTLMMessage(final String messageBody, final int expectedType) throws NTLMEngineException {
855             this(Base64.decodeBase64(messageBody.getBytes(DEFAULT_CHARSET)), expectedType);
856         }
857 
858         /** Constructor to use when message bytes are known */
859         NTLMMessage(final byte[] message, final int expectedType) throws NTLMEngineException {
860             messageContents = message;
861             // Look for NTLM message
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             // Check to be sure there's a type 2 message indicator next
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          * Get the length of the signature and flags, so calculations can adjust
886          * offsets accordingly.
887          */
888         protected int getPreambleLength() {
889             return SIGNATURE.length + 4;
890         }
891 
892         /** Get the message length */
893         protected int getMessageLength() {
894             return currentOutputPosition;
895         }
896 
897         /** Read a byte from a position within the message buffer */
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         /** Read a bunch of bytes from a position in the message buffer */
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         /** Read a ushort from a position within the message buffer */
914         protected int readUShort(final int position) {
915             return DavMailNTLMEngineImpl.readUShort(messageContents, position);
916         }
917 
918         /** Read a ulong from a position within the message buffer */
919         protected int readULong(final int position) {
920             return DavMailNTLMEngineImpl.readULong(messageContents, position);
921         }
922 
923         /** Read a security buffer from a position within the message buffer */
924         protected byte[] readSecurityBuffer(final int position) {
925             return DavMailNTLMEngineImpl.readSecurityBuffer(messageContents, position);
926         }
927 
928         /**
929          * Prepares the object to create a response of the given length.
930          *
931          * @param maxlength
932          *            the maximum length of the response to prepare,
933          *            including the type and the signature (which this method
934          *            adds).
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          * Adds the given byte to the response.
945          *
946          * @param b
947          *            the byte to add.
948          */
949         protected void addByte(final byte b) {
950             messageContents[currentOutputPosition] = b;
951             currentOutputPosition++;
952         }
953 
954         /**
955          * Adds the given bytes to the response.
956          *
957          * @param bytes
958          *            the bytes to add.
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         /** Adds a USHORT to the response */
971         protected void addUShort(final int value) {
972             addByte((byte) (value & 0xff));
973             addByte((byte) (value >> 8 & 0xff));
974         }
975 
976         /** Adds a ULong to the response */
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          * Returns the response that has been generated after shrinking the
986          * array if required and base64 encodes the response.
987          *
988          * @return The response as above.
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     /** Type 1 message assembly class */
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             // Strip off domain name from the host!
1028             final String unqualifiedHost = convertHost(host);
1029             // Use only the base domain name!
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                     //FLAG_WORKSTATION_PRESENT |
1048                     //FLAG_DOMAIN_PRESENT |
1049 
1050                     // Required flags
1051                     //FLAG_REQUEST_LAN_MANAGER_KEY |
1052                     FLAG_REQUEST_NTLMv1 |
1053                             FLAG_REQUEST_NTLM2_SESSION |
1054 
1055                             // Protocol version request
1056                             FLAG_REQUEST_VERSION |
1057 
1058                             // Recommended privacy settings
1059                             FLAG_REQUEST_ALWAYS_SIGN |
1060                             //FLAG_REQUEST_SEAL |
1061                             FLAG_REQUEST_SIGN | // for channel binding
1062 
1063                             // These must be set according to documentation, based on use of SEAL above
1064                             FLAG_REQUEST_128BIT_KEY_EXCH |
1065                             FLAG_REQUEST_56BIT_ENCRYPTION |
1066                             //FLAG_REQUEST_EXPLICIT_KEY_EXCH |
1067 
1068                             FLAG_REQUEST_UNICODE_ENCODING;
1069 
1070         }
1071 
1072         /**
1073          * Getting the response involves building the message before returning
1074          * it
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             // Now, build the message. Calculate its length first, including
1088             // signature or type.
1089             final int finalLength = 32 + 8 + hostBytesLength + domainBytesLength;
1090 
1091             // Set up the response. This will initialize the signature, message
1092             // type, and flags.
1093             prepareResponse(finalLength, 1);
1094 
1095             // Flags. These are the complete set of flags we support.
1096             addULong(flags);
1097 
1098             // Domain length (two times).
1099             addUShort(domainBytesLength);
1100             addUShort(domainBytesLength);
1101 
1102             // Domain offset.
1103             addULong(hostBytesLength + 32 + 8);
1104 
1105             // Host length (two times).
1106             addUShort(hostBytesLength);
1107             addUShort(hostBytesLength);
1108 
1109             // Host offset (always 32 + 8).
1110             addULong(32 + 8);
1111 
1112             // Version
1113             addUShort(0x0105);
1114             // Build
1115             addULong(2600);
1116             // NTLM revision
1117             addUShort(0x0f00);
1118 
1119             // Host (workstation) String.
1120             if (hostBytes != null) {
1121                 addBytes(hostBytes);
1122             }
1123             // Domain String.
1124             if (domainBytes != null) {
1125                 addBytes(domainBytes);
1126             }
1127         }
1128 
1129     }
1130 
1131     /** Type 2 message class */
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             // Type 2 message is laid out as follows:
1146             // First 8 bytes: NTLMSSP[0]
1147             // Next 4 bytes: Ulong, value 2
1148             // Next 8 bytes, starting at offset 12: target field (2 ushort lengths, 1 ulong offset)
1149             // Next 4 bytes, starting at offset 20: Flags, e.g. 0x22890235
1150             // Next 8 bytes, starting at offset 24: Challenge
1151             // Next 8 bytes, starting at offset 32: ??? (8 bytes of zeros)
1152             // Next 8 bytes, starting at offset 40: targetinfo field (2 ushort lengths, 1 ulong offset)
1153             // Next 2 bytes, major/minor version number (e.g. 0x05 0x02)
1154             // Next 8 bytes, build number
1155             // Next 2 bytes, protocol version number (e.g. 0x00 0x0f)
1156             // Next, various text fields, and a ushort of value 0 at the end
1157 
1158             // Parse out the rest of the info we need from the message
1159             // The nonce is the 8 bytes starting from the byte in position 24.
1160             challenge = new byte[8];
1161             readBytes(challenge, 24);
1162 
1163             flags = super.readULong(20);
1164 
1165             // Do the target!
1166             target = null;
1167             // The TARGET_DESIRED flag is said to not have understood semantics
1168             // in Type2 messages, so use the length of the packet to decide
1169             // how to proceed instead
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             // Do the target info!
1178             targetInfo = null;
1179             // TARGET_DESIRED flag cannot be relied on, so use packet length
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         /** Retrieve the challenge */
1189         byte[] getChallenge() {
1190             return challenge;
1191         }
1192 
1193         /** Retrieve the target */
1194         String getTarget() {
1195             return target;
1196         }
1197 
1198         /** Retrieve the target info */
1199         byte[] getTargetInfo() {
1200             return targetInfo;
1201         }
1202 
1203         /** Retrieve the response flags */
1204         int getFlags() {
1205             return flags;
1206         }
1207 
1208     }
1209 
1210     /** Type 3 message assembly class */
1211     static class Type3Message extends DavMailNTLMEngineImpl.NTLMMessage {
1212         // For mic computation
1213         protected final byte[] type1Message;
1214         protected final byte[] type2Message;
1215         // Response flags from the type2 message
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         /** More primitive constructor: don't include cert or previous messages.
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         /** More primitive constructor: don't include cert or previous messages.
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         /** Constructor. Pass the arguments we will need */
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         /** Constructor. Pass the arguments we will need */
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             // Save the flags
1290             this.type2Flags = type2Flags;
1291             this.type1Message = type1Message;
1292             this.type2Message = type2Message;
1293 
1294             // Strip off domain name from the host!
1295             final String unqualifiedHost = convertHost(host);
1296             // Use only the base domain name!
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             // Create a cipher generator class.  Use domain BEFORE it gets modified!
1308             final DavMailNTLMEngineImpl.CipherGen gen = new DavMailNTLMEngineImpl.CipherGen(currentTime,
1309                     unqualifiedDomain,
1310                     user,
1311                     password,
1312                     nonce,
1313                     target,
1314                     responseTargetInformation);
1315 
1316             // Use the new code to calculate the responses, including v2 if that
1317             // seems warranted.
1318             byte[] userSessionKey;
1319             try {
1320                 // This conditional may not work on Windows Server 2008 R2 and above, where it has not yet
1321                 // been tested
1322                 if (((type2Flags & FLAG_TARGETINFO_PRESENT) != 0) &&
1323                         targetInformation != null && target != null) {
1324                     // NTLMv2
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                     // NTLMv1
1334                     if ((type2Flags & FLAG_REQUEST_NTLM2_SESSION) != 0) {
1335                         // NTLM2 session stuff is requested
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                 // This likely means we couldn't find the MD4 hash algorithm -
1355                 // fail back to just using LM
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         /** Assemble the response */
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             // Calculate the layout within the packet
1412             final int lmRespOffset = 72 + // allocate space for the version
1413                     (computeMic ? 16 : 0); // and MIC
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             // Start the response. Length includes signature and type
1422             prepareResponse(finalLength, 3);
1423 
1424             // LM Resp Length (twice)
1425             addUShort(lmRespLen);
1426             addUShort(lmRespLen);
1427 
1428             // LM Resp Offset
1429             addULong(lmRespOffset);
1430 
1431             // NT Resp Length (twice)
1432             addUShort(ntRespLen);
1433             addUShort(ntRespLen);
1434 
1435             // NT Resp Offset
1436             addULong(ntRespOffset);
1437 
1438             // Domain length (twice)
1439             addUShort(domainLen);
1440             addUShort(domainLen);
1441 
1442             // Domain offset.
1443             addULong(domainOffset);
1444 
1445             // User Length (twice)
1446             addUShort(userLen);
1447             addUShort(userLen);
1448 
1449             // User offset
1450             addULong(userOffset);
1451 
1452             // Host length (twice)
1453             addUShort(hostLen);
1454             addUShort(hostLen);
1455 
1456             // Host offset
1457             addULong(hostOffset);
1458 
1459             // Session key length (twice)
1460             addUShort(sessionKeyLen);
1461             addUShort(sessionKeyLen);
1462 
1463             // Session key offset
1464             addULong(sessionKeyOffset);
1465 
1466             // Flags.
1467             addULong(
1468                     /*
1469                     //FLAG_WORKSTATION_PRESENT |
1470                     //FLAG_DOMAIN_PRESENT |
1471 
1472                     // Required flags
1473                     (type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) |
1474                     (type2Flags & FLAG_REQUEST_NTLMv1) |
1475                     (type2Flags & FLAG_REQUEST_NTLM2_SESSION) |
1476 
1477                     // Protocol version request
1478                     FLAG_REQUEST_VERSION |
1479 
1480                     // Recommended privacy settings
1481                     (type2Flags & FLAG_REQUEST_ALWAYS_SIGN) |
1482                     (type2Flags & FLAG_REQUEST_SEAL) |
1483                     (type2Flags & FLAG_REQUEST_SIGN) |
1484 
1485                     // These must be set according to documentation, based on use of SEAL above
1486                     (type2Flags & FLAG_REQUEST_128BIT_KEY_EXCH) |
1487                     (type2Flags & FLAG_REQUEST_56BIT_ENCRYPTION) |
1488                     (type2Flags & FLAG_REQUEST_EXPLICIT_KEY_EXCH) |
1489 
1490                     (type2Flags & FLAG_TARGETINFO_PRESENT) |
1491                     (type2Flags & FLAG_REQUEST_UNICODE_ENCODING) |
1492                     (type2Flags & FLAG_REQUEST_TARGET)
1493                         */
1494                     type2Flags
1495             );
1496 
1497             // Version
1498             addUShort(0x0105);
1499             // Build
1500             addULong(2600);
1501             // NTLM revision
1502             addUShort(0x0f00);
1503 
1504             int micPosition = -1;
1505             if (computeMic) {
1506                 micPosition = currentOutputPosition;
1507                 currentOutputPosition += 16;
1508             }
1509 
1510             // Add the actual data
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             // Write the mic back into its slot in the message
1521 
1522             if (computeMic) {
1523                 // Computation of message integrity code (MIC) as specified in [MS-NLMP] section 3.2.5.1.2.
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          * Add GSS channel binding hash and MIC flag to the targetInfo.
1535          * Looks like this is needed if we want to use exported session key for GSS wrapping.
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; // last tag is MSV_AV_EOL, do not copy that
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      * Cryptography support - MD4. The following class was based loosely on the
1610      * RFC and on code found at http://www.cs.umd.edu/~harry/jotp/src/md.java.
1611      * Code correctness was verified by looking at MD4.java from the jcifs
1612      * library (http://jcifs.samba.org). It was massaged extensively to the
1613      * final form found here by Karl Wright (kwright@metacarta.com).
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             // We always deal with 512 bits at a time. Correspondingly, there is
1628             // a buffer 64 bytes long that we write data into until it gets
1629             // full.
1630             int curBufferPos = (int) (count & 63L);
1631             int inputIndex = 0;
1632             while (input.length - inputIndex + curBufferPos >= dataBuffer.length) {
1633                 // We have enough data to do the next step. Do a partial copy
1634                 // and a transform, updating inputIndex and curBufferPos
1635                 // accordingly
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             // If there's anything left, copy it into the buffer and leave it.
1645             // We know there's not enough left to process.
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             // Feed pad/length data into engine. This must round out the input
1655             // to a multiple of 512 bits.
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             // Leading 0x80, specified amount of zero padding, then length in
1660             // bits.
1661             postBytes[0] = (byte) 0x80;
1662             // Fill out the last 8 bytes with the length
1663             for (int i = 0; i < 8; i++) {
1664                 postBytes[padLen + i] = (byte) ((count * 8) >>> (8 * i));
1665             }
1666 
1667             // Update the engine
1668             update(postBytes);
1669 
1670             // Calculate final result
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             // Convert current buffer to 16 ulongs
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             // Do a round of processing
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      * Cryptography support - HMACMD5 - algorithmically based on various web
1776      * resources by Karl Wright
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             // Initialize the pad buffers with the key
1788             ipad = new byte[64];
1789             opad = new byte[64];
1790 
1791             int keyLength = key.length;
1792             if (keyLength > 64) {
1793                 // Use MD5 of the key instead, as described in RFC 2104
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             // Very important: processChallenge the digest with the ipad buffer
1811             md5.reset();
1812             md5.update(ipad);
1813 
1814         }
1815 
1816         /** Grab the current digest. This is the "answer". */
1817         byte[] getOutput() {
1818             final byte[] digest = md5.digest();
1819             md5.update(opad);
1820             return md5.digest(digest);
1821         }
1822 
1823         /** Update by adding a complete array */
1824         void update(final byte[] input) {
1825             md5.update(input);
1826         }
1827 
1828         /** Update the algorithm */
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         // need to retrieve raw type 1 and 2 message and connection certificate for channel binding implementation
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 }