Package: Ntlm
Ntlm
name | instruction | branch | complexity | line | method | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Ntlm(String, String, String, String, MailLogger) |
|
|
|
|
|
||||||||||||||||||||
calcLMHash() |
|
|
|
|
|
||||||||||||||||||||
calcNTHash() |
|
|
|
|
|
||||||||||||||||||||
calcResponse(byte[], byte[]) |
|
|
|
|
|
||||||||||||||||||||
calcV2Response(byte[], byte[], byte[]) |
|
|
|
|
|
||||||||||||||||||||
copybytes(byte[], int, String, String) |
|
|
|
|
|
||||||||||||||||||||
generateType1Msg(int) |
|
|
|
|
|
||||||||||||||||||||
generateType1Msg(int, boolean) |
|
|
|
|
|
||||||||||||||||||||
generateType3Msg(String) |
|
|
|
|
|
||||||||||||||||||||
hmacMD5(byte[], byte[]) |
|
|
|
|
|
||||||||||||||||||||
init0() |
|
|
|
|
|
||||||||||||||||||||
makeDesKey(byte[], int) |
|
|
|
|
|
||||||||||||||||||||
readInt(byte[], int) |
|
|
|
|
|
||||||||||||||||||||
readShort(byte[], int) |
|
|
|
|
|
||||||||||||||||||||
static {...} |
|
|
|
|
|
||||||||||||||||||||
toHex(byte[]) |
|
|
|
|
|
||||||||||||||||||||
writeInt(byte[], int, int) |
|
|
|
|
|
||||||||||||||||||||
writeShort(byte[], int, int) |
|
|
|
|
|
Coverage
1: /*
2: * Copyright (c) 2005, 2023 Oracle and/or its affiliates. All rights reserved.
3: *
4: * This program and the accompanying materials are made available under the
5: * terms of the Eclipse Public License v. 2.0, which is available at
6: * http://www.eclipse.org/legal/epl-2.0.
7: *
8: * This Source Code may also be made available under the following Secondary
9: * Licenses when the conditions for such availability set forth in the
10: * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11: * version 2 with the GNU Classpath Exception, which is available at
12: * https://www.gnu.org/software/classpath/license.html.
13: *
14: * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15: */
16:
17: /*
18: * Copied from OpenJDK with permission.
19: */
20:
21: package org.eclipse.angus.mail.auth;
22:
23: import java.io.UnsupportedEncodingException;
24: import java.security.GeneralSecurityException;
25: import java.security.InvalidKeyException;
26: import java.security.NoSuchAlgorithmException;
27: import java.util.Base64;
28: import java.util.Locale;
29: import java.util.Random;
30: import java.util.logging.Level;
31:
32: import javax.crypto.Cipher;
33: import javax.crypto.Mac;
34: import javax.crypto.NoSuchPaddingException;
35: import javax.crypto.SecretKey;
36: import javax.crypto.SecretKeyFactory;
37: import javax.crypto.spec.DESKeySpec;
38: import javax.crypto.spec.SecretKeySpec;
39:
40: import org.eclipse.angus.mail.util.MailLogger;
41:
42:
43: /**
44: * NTLMAuthentication:
45: *
46: * @author Michael McMahon
47: * @author Bill Shannon (adapted for Jakarta Mail)
48: */
49: public class Ntlm {
50:
51: private byte[] type1;
52: private byte[] type3;
53:
54: private SecretKeyFactory fac;
55: private Cipher cipher;
56: private MD4 md4;
57: private String hostname;
58: private String ntdomain;
59: private String username;
60: private String password;
61:
62: private Mac hmac;
63:
64: private MailLogger logger;
65:
66: // NTLM flags, as defined in Microsoft NTLM spec
67: // https://msdn.microsoft.com/en-us/library/cc236621.aspx
68: private static final int NTLMSSP_NEGOTIATE_UNICODE        = 0x00000001;
69: private static final int NTLMSSP_NEGOTIATE_OEM        = 0x00000002;
70: private static final int NTLMSSP_REQUEST_TARGET        = 0x00000004;
71: private static final int NTLMSSP_NEGOTIATE_SIGN        = 0x00000010;
72: private static final int NTLMSSP_NEGOTIATE_SEAL        = 0x00000020;
73: private static final int NTLMSSP_NEGOTIATE_DATAGRAM        = 0x00000040;
74: private static final int NTLMSSP_NEGOTIATE_LM_KEY        = 0x00000080;
75: private static final int NTLMSSP_NEGOTIATE_NTLM        = 0x00000200;
76: private static final int NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED        = 0x00001000;
77: private static final int NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED        = 0x00002000;
78: private static final int NTLMSSP_NEGOTIATE_ALWAYS_SIGN        = 0x00008000;
79: private static final int NTLMSSP_TARGET_TYPE_DOMAIN        = 0x00010000;
80: private static final int NTLMSSP_TARGET_TYPE_SERVER        = 0x00020000;
81: private static final int NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY        = 0x00080000;
82: private static final int NTLMSSP_NEGOTIATE_IDENTIFY        = 0x00100000;
83: private static final int NTLMSSP_REQUEST_NON_NT_SESSION_KEY        = 0x00400000;
84: private static final int NTLMSSP_NEGOTIATE_TARGET_INFO        = 0x00800000;
85: private static final int NTLMSSP_NEGOTIATE_VERSION        = 0x02000000;
86: private static final int NTLMSSP_NEGOTIATE_128        = 0x20000000;
87: private static final int NTLMSSP_NEGOTIATE_KEY_EXCH        = 0x40000000;
88: private static final int NTLMSSP_NEGOTIATE_56        = 0x80000000;
89:
90: private static final byte RESPONSERVERSION = 1;
91: private static final byte HIRESPONSERVERSION = 1;
92: private static final byte[] Z6 = new byte[] { 0, 0, 0, 0, 0, 0 };
93: private static final byte[] Z4 = new byte[] { 0, 0, 0, 0 };
94:
95: private void init0() {
96: type1 = new byte[256];        // hopefully large enough
97: type3 = new byte[512];        // ditto
98: System.arraycopy(new byte[] {'N','T','L','M','S','S','P',0,1}, 0,
99:                          type1, 0, 9);
100: System.arraycopy(new byte[] {'N','T','L','M','S','S','P',0,3}, 0,
101:                          type3, 0, 9);
102:
103: try {
104: fac = SecretKeyFactory.getInstance("DES");
105: cipher = Cipher.getInstance("DES/ECB/NoPadding");
106: md4 = new MD4();
107: } catch (NoSuchPaddingException e) {
108: assert false;
109: } catch (NoSuchAlgorithmException e) {
110: assert false;
111: }
112: };
113:
114: /**
115: * Create an NTLM authenticator.
116: * Username may be specified as domain\\username in the Authenticator.
117: * If this notation is not used, then the domain will be taken
118: * from the ntdomain parameter.
119: *
120: * @param        ntdomain        the NT domain
121: * @param        hostname        the host name
122: * @param        username        the user name
123: * @param        password        the password
124: * @param        logger                the MailLogger
125: */
126: public Ntlm(String ntdomain, String hostname, String username,
127:                                 String password, MailLogger logger) {
128:         int i = hostname.indexOf('.');
129:•        if (i != -1) {
130:          hostname = hostname.substring(0, i);
131:         }
132: i = username.indexOf('\\');
133:• if (i != -1) {
134: ntdomain = username.substring(0, i).toUpperCase(Locale.ENGLISH);
135: username = username.substring(i+1);
136:• } else if (ntdomain == null) {
137:          ntdomain = "";
138:         }
139:         this.ntdomain = ntdomain;
140:         this.hostname = hostname;
141:         this.username = username;
142:         this.password = password;
143:         this.logger = logger.getLogger(this.getClass(), "DEBUG NTLM");
144: init0();
145: }
146:
147: private void copybytes(byte[] dest, int destpos, String src, String enc) {
148: try {
149: byte[] x = src.getBytes(enc);
150: System.arraycopy(x, 0, dest, destpos, x.length);
151: } catch (UnsupportedEncodingException e) {
152: assert false;
153: }
154: }
155:
156: // for compatibility, just in case
157: public String generateType1Msg(int flags) {
158:         return generateType1Msg(flags, false);
159: }
160:
161: public String generateType1Msg(int flags, boolean v2) {
162: int dlen = ntdomain.length();
163:         int type1flags =
164:                 NTLMSSP_NEGOTIATE_UNICODE |
165:                 NTLMSSP_NEGOTIATE_OEM |
166:                 NTLMSSP_NEGOTIATE_NTLM |
167:                 NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED |
168:                 NTLMSSP_NEGOTIATE_ALWAYS_SIGN |
169:                 flags;
170:•        if (dlen != 0)
171:          type1flags |= NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED;
172:•        if (v2)
173:          type1flags |= NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY;
174:         writeInt(type1, 12, type1flags);
175: type1[28] = (byte) 0x20;        // host name offset
176:         writeShort(type1, 16, dlen);
177:         writeShort(type1, 18, dlen);
178:
179: int hlen = hostname.length();
180:         writeShort(type1, 24, hlen);
181:         writeShort(type1, 26, hlen);
182:
183: copybytes(type1, 32, hostname, "iso-8859-1");
184: copybytes(type1, hlen+32, ntdomain, "iso-8859-1");
185:         writeInt(type1, 20, hlen+32);
186:
187: byte[] msg = new byte[32 + hlen + dlen];
188: System.arraycopy(type1, 0, msg, 0, 32 + hlen + dlen);
189:•        if (logger.isLoggable(Level.FINE))
190:          logger.fine("type 1 message: " + toHex(msg));
191:
192: String result = null;
193:         try {
194:          result = new String(Base64.getEncoder().encode(msg), "iso-8859-1");
195: } catch (UnsupportedEncodingException e) {
196: assert false;
197: }
198: return result;
199: }
200:
201: /**
202: * Convert a 7 byte array to an 8 byte array (for a des key with parity).
203: * Input starts at offset off.
204: */
205: private byte[] makeDesKey(byte[] input, int off) {
206: int[] in = new int[input.length];
207:• for (int i = 0; i < in.length; i++) {
208:• in[i] = input[i] < 0 ? input[i] + 256: input[i];
209: }
210: byte[] out = new byte[8];
211: out[0] = (byte)in[off+0];
212: out[1] = (byte)(((in[off+0] << 7) & 0xFF) | (in[off+1] >> 1));
213: out[2] = (byte)(((in[off+1] << 6) & 0xFF) | (in[off+2] >> 2));
214: out[3] = (byte)(((in[off+2] << 5) & 0xFF) | (in[off+3] >> 3));
215: out[4] = (byte)(((in[off+3] << 4) & 0xFF) | (in[off+4] >> 4));
216: out[5] = (byte)(((in[off+4] << 3) & 0xFF) | (in[off+5] >> 5));
217: out[6] = (byte)(((in[off+5] << 2) & 0xFF) | (in[off+6] >> 6));
218: out[7] = (byte)((in[off+6] << 1) & 0xFF);
219: return out;
220: }
221:
222: /**
223: * Compute hash-based message authentication code for NTLMv2.
224: */
225: private byte[] hmacMD5(byte[] key, byte[] text) {
226:         try {
227:•         if (hmac == null)
228:                 hmac = Mac.getInstance("HmacMD5");
229:         } catch (NoSuchAlgorithmException ex) {
230:          throw new AssertionError();
231:         }
232:         try {
233:          byte[] nk = new byte[16];
234:•         System.arraycopy(key, 0, nk, 0, key.length > 16 ? 16 : key.length);
235:          SecretKeySpec skey = new SecretKeySpec(nk, "HmacMD5");
236:          hmac.init(skey);
237:          return hmac.doFinal(text);
238:         } catch (InvalidKeyException ex) {
239:          assert false;
240:         } catch (RuntimeException e) {
241:          assert false;
242:         }
243:         return null;
244: }
245:
246: private byte[] calcLMHash() throws GeneralSecurityException {
247: byte[] magic = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25};
248: byte[] pwb = null;
249:         try {
250:          pwb = password.toUpperCase(Locale.ENGLISH).getBytes("iso-8859-1");
251:         } catch (UnsupportedEncodingException ex) {
252:          // should never happen
253:          assert false;
254:         }
255: byte[] pwb1 = new byte[14];
256: int len = password.length();
257:• if (len > 14)
258: len = 14;
259: System.arraycopy(pwb, 0, pwb1, 0, len); /* Zero padded */
260:
261: DESKeySpec dks1 = new DESKeySpec(makeDesKey(pwb1, 0));
262: DESKeySpec dks2 = new DESKeySpec(makeDesKey(pwb1, 7));
263:
264: SecretKey key1 = fac.generateSecret(dks1);
265: SecretKey key2 = fac.generateSecret(dks2);
266: cipher.init(Cipher.ENCRYPT_MODE, key1);
267: byte[] out1 = cipher.doFinal(magic, 0, 8);
268: cipher.init(Cipher.ENCRYPT_MODE, key2);
269: byte[] out2 = cipher.doFinal(magic, 0, 8);
270:
271: byte[] result = new byte [21];
272: System.arraycopy(out1, 0, result, 0, 8);
273: System.arraycopy(out2, 0, result, 8, 8);
274: return result;
275: }
276:
277: private byte[] calcNTHash() throws GeneralSecurityException {
278: byte[] pw = null;
279: try {
280: pw = password.getBytes("UnicodeLittleUnmarked");
281: } catch (UnsupportedEncodingException e) {
282: assert false;
283: }
284: byte[] out = md4.digest(pw);
285: byte[] result = new byte[21];
286: System.arraycopy(out, 0, result, 0, 16);
287: return result;
288: }
289:
290: /*
291: * Key is a 21 byte array. Split it into 3 7 byte chunks,
292: * convert each to 8 byte DES keys, encrypt the text arg with
293: * each key and return the three results in a sequential [].
294: */
295: private byte[] calcResponse(byte[] key, byte[] text)
296:                                 throws GeneralSecurityException {
297:• assert key.length == 21;
298: DESKeySpec dks1 = new DESKeySpec(makeDesKey(key, 0));
299: DESKeySpec dks2 = new DESKeySpec(makeDesKey(key, 7));
300: DESKeySpec dks3 = new DESKeySpec(makeDesKey(key, 14));
301: SecretKey key1 = fac.generateSecret(dks1);
302: SecretKey key2 = fac.generateSecret(dks2);
303: SecretKey key3 = fac.generateSecret(dks3);
304: cipher.init(Cipher.ENCRYPT_MODE, key1);
305: byte[] out1 = cipher.doFinal(text, 0, 8);
306: cipher.init(Cipher.ENCRYPT_MODE, key2);
307: byte[] out2 = cipher.doFinal(text, 0, 8);
308: cipher.init(Cipher.ENCRYPT_MODE, key3);
309: byte[] out3 = cipher.doFinal(text, 0, 8);
310: byte[] result = new byte[24];
311: System.arraycopy(out1, 0, result, 0, 8);
312: System.arraycopy(out2, 0, result, 8, 8);
313: System.arraycopy(out3, 0, result, 16, 8);
314: return result;
315: }
316:
317: /*
318: * Calculate the NTLMv2 response based on the nthash, additional data,
319: * and the original challenge.
320: */
321: private byte[] calcV2Response(byte[] nthash, byte[] blob, byte[] challenge)
322:                                 throws GeneralSecurityException {
323: byte[] txt = null;
324:         try {
325:          txt = (username.toUpperCase(Locale.ENGLISH) + ntdomain).
326:                          getBytes("UnicodeLittleUnmarked");
327:         } catch (UnsupportedEncodingException ex) {
328:          // should never happen
329:          assert false;
330:         }
331:         byte[] ntlmv2hash = hmacMD5(nthash, txt);
332:         byte[] cb = new byte[blob.length + 8];
333:         System.arraycopy(challenge, 0, cb, 0, 8);
334:         System.arraycopy(blob, 0, cb, 8, blob.length);
335:         byte[] result = new byte[blob.length + 16];
336:         System.arraycopy(hmacMD5(ntlmv2hash, cb), 0, result, 0, 16);
337:         System.arraycopy(blob, 0, result, 16, blob.length);
338:         return result;
339: }
340:
341: public String generateType3Msg(String type2msg) {
342:         try {
343:
344:         /* First decode the type2 message to get the server challenge */
345:         /* challenge is located at type2[24] for 8 bytes */
346:         byte[] type2 = null;
347:         try {
348:          type2 = Base64.getDecoder().decode(type2msg.getBytes("us-ascii"));
349:         } catch (UnsupportedEncodingException ex) {
350:          // should never happen
351:          assert false;
352:         }
353:•        if (logger.isLoggable(Level.FINE))
354:          logger.fine("type 2 message: " + toHex(type2));
355:
356: byte[] challenge = new byte[8];
357: System.arraycopy(type2, 24, challenge, 0, 8);
358:
359:         int type3flags =
360:                 NTLMSSP_NEGOTIATE_UNICODE |
361:                 NTLMSSP_NEGOTIATE_NTLM |
362:                 NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
363:
364: int ulen = username.length()*2;
365:         writeShort(type3, 36, ulen);
366:         writeShort(type3, 38, ulen);
367: int dlen = ntdomain.length()*2;
368:         writeShort(type3, 28, dlen);
369:         writeShort(type3, 30, dlen);
370: int hlen = hostname.length()*2;
371:         writeShort(type3, 44, hlen);
372:         writeShort(type3, 46, hlen);
373:
374: int l = 64;
375: copybytes(type3, l, ntdomain, "UnicodeLittleUnmarked");
376:         writeInt(type3, 32, l);
377: l += dlen;
378: copybytes(type3, l, username, "UnicodeLittleUnmarked");
379:         writeInt(type3, 40, l);
380: l += ulen;
381: copybytes(type3, l, hostname, "UnicodeLittleUnmarked");
382:         writeInt(type3, 48, l);
383: l += hlen;
384:
385: byte[] msg = null;
386:         byte[] lmresponse = null;
387:         byte[] ntresponse = null;
388:         int flags = readInt(type2, 20);
389:
390:         // did the server agree to NTLMv2?
391:•        if ((flags & NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY) != 0) {
392:          // yes, create an NTLMv2 response
393:          logger.fine("Using NTLMv2");
394:          type3flags |= NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY;
395:          byte[] nonce = new byte[8];
396:          // XXX - allow user to specify Random instance via properties?
397:          (new Random()).nextBytes(nonce);
398:          byte[] nthash = calcNTHash();
399:          lmresponse = calcV2Response(nthash, nonce, challenge);
400:          byte[] targetInfo = new byte[0];
401:•         if ((flags & NTLMSSP_NEGOTIATE_TARGET_INFO) != 0) {
402:                 int tlen = readShort(type2, 40);
403:                 int toff = readInt(type2, 44);
404:                 targetInfo = new byte[tlen];
405:                 System.arraycopy(type2, toff, targetInfo, 0, tlen);
406:          }
407:          byte[] blob = new byte[32 + targetInfo.length];
408:          blob[0] = RESPONSERVERSION;
409:          blob[1] = HIRESPONSERVERSION;
410:          System.arraycopy(Z6, 0, blob, 2, 6);
411:          // convert time to NT format
412:          long now = (System.currentTimeMillis() + 11644473600000L) * 10000L;
413:•         for (int i = 0; i < 8; i++) {
414:                 blob[8 + i] = (byte)(now & 0xff);
415:                 now >>= 8;
416:          }
417:          System.arraycopy(nonce, 0, blob, 16, 8);
418:          System.arraycopy(Z4, 0, blob, 24, 4);
419:          System.arraycopy(targetInfo, 0, blob, 28, targetInfo.length);
420:          System.arraycopy(Z4, 0, blob, 28 + targetInfo.length, 4);
421:          ntresponse = calcV2Response(nthash, blob, challenge);
422:         } else {
423:          byte[] lmhash = calcLMHash();
424:          lmresponse = calcResponse(lmhash, challenge);
425:          byte[] nthash = calcNTHash();
426:          ntresponse = calcResponse(nthash, challenge);
427:         }
428:         System.arraycopy(lmresponse, 0, type3, l, lmresponse.length);
429:         writeShort(type3, 12, lmresponse.length);
430:         writeShort(type3, 14, lmresponse.length);
431:         writeInt(type3, 16, l);
432:         l += 24;
433:         System.arraycopy(ntresponse, 0, type3, l, ntresponse.length);
434:         writeShort(type3, 20, ntresponse.length);
435:         writeShort(type3, 22, ntresponse.length);
436:         writeInt(type3, 24, l);
437:         l += ntresponse.length;
438:         writeShort(type3, 56, l);
439:
440:         msg = new byte[l];
441:         System.arraycopy(type3, 0, msg, 0, l);
442:
443:         writeInt(type3, 60, type3flags);
444:
445:•        if (logger.isLoggable(Level.FINE))
446:          logger.fine("type 3 message: " + toHex(msg));
447:
448: String result = null;
449:         try {
450:          result = new String(Base64.getEncoder().encode(msg), "iso-8859-1");
451: } catch (UnsupportedEncodingException e) {
452: assert false;
453: }
454: return result;
455:
456:         } catch (GeneralSecurityException ex) {
457:          // should never happen
458:          logger.log(Level.FINE, "GeneralSecurityException", ex);
459:          return "";        // will fail later
460:         }
461: }
462:
463: private static int readShort(byte[] b, int off) {
464:         return (((int)b[off]) & 0xff) |
465:          ((((int)b[off+1]) & 0xff) << 8);
466: }
467:
468: private void writeShort(byte[] b, int off, int data) {
469: b[off] = (byte) (data & 0xff);
470: b[off+1] = (byte) ((data >> 8) & 0xff);
471: }
472:
473: private static int readInt(byte[] b, int off) {
474:         return (((int)b[off]) & 0xff) |
475:          ((((int)b[off+1]) & 0xff) << 8) |
476:          ((((int)b[off+2]) & 0xff) << 16) |
477:          ((((int)b[off+3]) & 0xff) << 24);
478: }
479:
480: private void writeInt(byte[] b, int off, int data) {
481: b[off] = (byte) (data & 0xff);
482: b[off+1] = (byte) ((data >> 8) & 0xff);
483: b[off+2] = (byte) ((data >> 16) & 0xff);
484: b[off+3] = (byte) ((data >> 24) & 0xff);
485: }
486:
487: private static char[] hex =
488:         { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' };
489:
490: private static String toHex(byte[] b) {
491:         StringBuilder sb = new StringBuilder(b.length * 3);
492:•        for (int i = 0; i < b.length; i++)
493:          sb.append(hex[(b[i]>>4)&0xF]).append(hex[b[i]&0xF]).append(' ');
494:         return sb.toString();
495: }
496: }