|
1 /* |
|
2 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. Oracle designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Oracle in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 * or visit www.oracle.com if you need additional information or have any |
|
23 * questions. |
|
24 */ |
|
25 |
|
26 package com.sun.security.ntlm; |
|
27 |
|
28 import static com.sun.security.ntlm.Version.*; |
|
29 import java.io.IOException; |
|
30 import java.io.UnsupportedEncodingException; |
|
31 import java.security.InvalidKeyException; |
|
32 import java.security.MessageDigest; |
|
33 import java.security.NoSuchAlgorithmException; |
|
34 import java.security.spec.InvalidKeySpecException; |
|
35 import java.util.Arrays; |
|
36 import javax.crypto.BadPaddingException; |
|
37 import javax.crypto.Cipher; |
|
38 import javax.crypto.IllegalBlockSizeException; |
|
39 import javax.crypto.Mac; |
|
40 import javax.crypto.NoSuchPaddingException; |
|
41 import javax.crypto.SecretKey; |
|
42 import javax.crypto.SecretKeyFactory; |
|
43 import javax.crypto.spec.DESKeySpec; |
|
44 import javax.crypto.spec.SecretKeySpec; |
|
45 |
|
46 /** |
|
47 * NTLM authentication implemented according to MS-NLMP, version 12.1 |
|
48 * @since 1.7 |
|
49 */ |
|
50 class NTLM { |
|
51 |
|
52 private final SecretKeyFactory fac; |
|
53 private final Cipher cipher; |
|
54 private final MessageDigest md4; |
|
55 private final Mac hmac; |
|
56 private final MessageDigest md5; |
|
57 private static final boolean DEBUG = |
|
58 System.getProperty("ntlm.debug") != null; |
|
59 |
|
60 final Version v; |
|
61 |
|
62 final boolean writeLM; |
|
63 final boolean writeNTLM; |
|
64 |
|
65 protected NTLM(String version) throws NTLMException { |
|
66 if (version == null) version = "LMv2/NTLMv2"; |
|
67 switch (version) { |
|
68 case "LM": v = NTLM; writeLM = true; writeNTLM = false; break; |
|
69 case "NTLM": v = NTLM; writeLM = false; writeNTLM = true; break; |
|
70 case "LM/NTLM": v = NTLM; writeLM = writeNTLM = true; break; |
|
71 case "NTLM2": v = NTLM2; writeLM = writeNTLM = true; break; |
|
72 case "LMv2": v = NTLMv2; writeLM = true; writeNTLM = false; break; |
|
73 case "NTLMv2": v = NTLMv2; writeLM = false; writeNTLM = true; break; |
|
74 case "LMv2/NTLMv2": v = NTLMv2; writeLM = writeNTLM = true; break; |
|
75 default: throw new NTLMException(NTLMException.BAD_VERSION, |
|
76 "Unknown version " + version); |
|
77 } |
|
78 try { |
|
79 fac = SecretKeyFactory.getInstance ("DES"); |
|
80 cipher = Cipher.getInstance ("DES/ECB/NoPadding"); |
|
81 md4 = sun.security.provider.MD4.getInstance(); |
|
82 hmac = Mac.getInstance("HmacMD5"); |
|
83 md5 = MessageDigest.getInstance("MD5"); |
|
84 } catch (NoSuchPaddingException e) { |
|
85 throw new AssertionError(); |
|
86 } catch (NoSuchAlgorithmException e) { |
|
87 throw new AssertionError(); |
|
88 } |
|
89 } |
|
90 |
|
91 /** |
|
92 * Prints out a formatted string, called in various places inside then NTLM |
|
93 * implementation for debugging/logging purposes. When the system property |
|
94 * "ntlm.debug" is set, <code>System.out.printf(format, args)</code> is |
|
95 * called. This method is designed to be overridden by child classes to |
|
96 * match their own debugging/logging mechanisms. |
|
97 * @param format a format string |
|
98 * @param args the arguments referenced by <code>format</code> |
|
99 * @see java.io.PrintStream#printf(java.lang.String, java.lang.Object[]) |
|
100 */ |
|
101 public void debug(String format, Object... args) { |
|
102 if (DEBUG) { |
|
103 System.out.printf(format, args); |
|
104 } |
|
105 } |
|
106 |
|
107 /** |
|
108 * Prints out the content of a byte array, called in various places inside |
|
109 * the NTLM implementation for debugging/logging purposes. When the system |
|
110 * property "ntlm.debug" is set, the hexdump of the array is printed into |
|
111 * System.out. This method is designed to be overridden by child classes to |
|
112 * match their own debugging/logging mechanisms. |
|
113 * @param bytes the byte array to print out |
|
114 */ |
|
115 public void debug(byte[] bytes) { |
|
116 if (DEBUG) { |
|
117 try { |
|
118 new sun.misc.HexDumpEncoder().encodeBuffer(bytes, System.out); |
|
119 } catch (IOException ioe) { |
|
120 // Impossible |
|
121 } |
|
122 } |
|
123 } |
|
124 |
|
125 /** |
|
126 * Reading an NTLM packet |
|
127 */ |
|
128 static class Reader { |
|
129 |
|
130 private final byte[] internal; |
|
131 |
|
132 Reader(byte[] data) { |
|
133 internal = data; |
|
134 } |
|
135 |
|
136 int readInt(int offset) throws NTLMException { |
|
137 try { |
|
138 return internal[offset] & 0xff + |
|
139 (internal[offset+1] & 0xff << 8) + |
|
140 (internal[offset+2] & 0xff << 16) + |
|
141 (internal[offset+3] & 0xff << 24); |
|
142 } catch (ArrayIndexOutOfBoundsException ex) { |
|
143 throw new NTLMException(NTLMException.PACKET_READ_ERROR, |
|
144 "Input message incorrect size"); |
|
145 } |
|
146 } |
|
147 |
|
148 int readShort(int offset) throws NTLMException { |
|
149 try { |
|
150 return internal[offset] & 0xff + |
|
151 (internal[offset+1] & 0xff << 8); |
|
152 } catch (ArrayIndexOutOfBoundsException ex) { |
|
153 throw new NTLMException(NTLMException.PACKET_READ_ERROR, |
|
154 "Input message incorrect size"); |
|
155 } |
|
156 } |
|
157 |
|
158 byte[] readBytes(int offset, int len) throws NTLMException { |
|
159 try { |
|
160 return Arrays.copyOfRange(internal, offset, offset + len); |
|
161 } catch (ArrayIndexOutOfBoundsException ex) { |
|
162 throw new NTLMException(NTLMException.PACKET_READ_ERROR, |
|
163 "Input message incorrect size"); |
|
164 } |
|
165 } |
|
166 |
|
167 byte[] readSecurityBuffer(int offset) throws NTLMException { |
|
168 int pos = readInt(offset+4); |
|
169 if (pos == 0) return null; |
|
170 try { |
|
171 return Arrays.copyOfRange( |
|
172 internal, pos, pos + readShort(offset)); |
|
173 } catch (ArrayIndexOutOfBoundsException ex) { |
|
174 throw new NTLMException(NTLMException.PACKET_READ_ERROR, |
|
175 "Input message incorrect size"); |
|
176 } |
|
177 } |
|
178 |
|
179 String readSecurityBuffer(int offset, boolean unicode) |
|
180 throws NTLMException { |
|
181 byte[] raw = readSecurityBuffer(offset); |
|
182 try { |
|
183 return raw == null ? null : new String( |
|
184 raw, unicode ? "UnicodeLittleUnmarked" : "ISO8859_1"); |
|
185 } catch (UnsupportedEncodingException ex) { |
|
186 throw new NTLMException(NTLMException.PACKET_READ_ERROR, |
|
187 "Invalid input encoding"); |
|
188 } |
|
189 } |
|
190 } |
|
191 |
|
192 /** |
|
193 * Writing an NTLM packet |
|
194 */ |
|
195 static class Writer { |
|
196 |
|
197 private byte[] internal; // buffer |
|
198 private int current; // current written content interface buffer |
|
199 |
|
200 /** |
|
201 * Starts writing a NTLM packet |
|
202 * @param type NEGOTIATE || CHALLENGE || AUTHENTICATE |
|
203 * @param len the base length, without security buffers |
|
204 */ |
|
205 Writer(int type, int len) { |
|
206 assert len < 256; |
|
207 internal = new byte[256]; |
|
208 current = len; |
|
209 System.arraycopy ( |
|
210 new byte[] {'N','T','L','M','S','S','P',0,(byte)type}, |
|
211 0, internal, 0, 9); |
|
212 } |
|
213 |
|
214 void writeShort(int offset, int number) { |
|
215 internal[offset] = (byte)(number); |
|
216 internal[offset+1] = (byte)(number >> 8); |
|
217 } |
|
218 |
|
219 void writeInt(int offset, int number) { |
|
220 internal[offset] = (byte)(number); |
|
221 internal[offset+1] = (byte)(number >> 8); |
|
222 internal[offset+2] = (byte)(number >> 16); |
|
223 internal[offset+3] = (byte)(number >> 24); |
|
224 } |
|
225 |
|
226 void writeBytes(int offset, byte[] data) { |
|
227 System.arraycopy(data, 0, internal, offset, data.length); |
|
228 } |
|
229 |
|
230 void writeSecurityBuffer(int offset, byte[] data) { |
|
231 if (data == null) { |
|
232 writeShort(offset+4, current); |
|
233 } else { |
|
234 int len = data.length; |
|
235 if (current + len > internal.length) { |
|
236 internal = Arrays.copyOf(internal, current + len + 256); |
|
237 } |
|
238 writeShort(offset, len); |
|
239 writeShort(offset+2, len); |
|
240 writeShort(offset+4, current); |
|
241 System.arraycopy(data, 0, internal, current, len); |
|
242 current += len; |
|
243 } |
|
244 } |
|
245 |
|
246 void writeSecurityBuffer(int offset, String str, boolean unicode) { |
|
247 try { |
|
248 writeSecurityBuffer(offset, str == null ? null : str.getBytes( |
|
249 unicode ? "UnicodeLittleUnmarked" : "ISO8859_1")); |
|
250 } catch (UnsupportedEncodingException ex) { |
|
251 assert false; |
|
252 } |
|
253 } |
|
254 |
|
255 byte[] getBytes() { |
|
256 return Arrays.copyOf(internal, current); |
|
257 } |
|
258 } |
|
259 |
|
260 // LM/NTLM |
|
261 |
|
262 /* Convert a 7 byte array to an 8 byte array (for a des key with parity) |
|
263 * input starts at offset off |
|
264 */ |
|
265 byte[] makeDesKey (byte[] input, int off) { |
|
266 int[] in = new int [input.length]; |
|
267 for (int i=0; i<in.length; i++ ) { |
|
268 in[i] = input[i]<0 ? input[i]+256: input[i]; |
|
269 } |
|
270 byte[] out = new byte[8]; |
|
271 out[0] = (byte)in[off+0]; |
|
272 out[1] = (byte)(((in[off+0] << 7) & 0xFF) | (in[off+1] >> 1)); |
|
273 out[2] = (byte)(((in[off+1] << 6) & 0xFF) | (in[off+2] >> 2)); |
|
274 out[3] = (byte)(((in[off+2] << 5) & 0xFF) | (in[off+3] >> 3)); |
|
275 out[4] = (byte)(((in[off+3] << 4) & 0xFF) | (in[off+4] >> 4)); |
|
276 out[5] = (byte)(((in[off+4] << 3) & 0xFF) | (in[off+5] >> 5)); |
|
277 out[6] = (byte)(((in[off+5] << 2) & 0xFF) | (in[off+6] >> 6)); |
|
278 out[7] = (byte)((in[off+6] << 1) & 0xFF); |
|
279 return out; |
|
280 } |
|
281 |
|
282 byte[] calcLMHash (byte[] pwb) { |
|
283 byte[] magic = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25}; |
|
284 byte[] pwb1 = new byte [14]; |
|
285 int len = pwb.length; |
|
286 if (len > 14) |
|
287 len = 14; |
|
288 System.arraycopy (pwb, 0, pwb1, 0, len); /* Zero padded */ |
|
289 |
|
290 try { |
|
291 DESKeySpec dks1 = new DESKeySpec (makeDesKey (pwb1, 0)); |
|
292 DESKeySpec dks2 = new DESKeySpec (makeDesKey (pwb1, 7)); |
|
293 |
|
294 SecretKey key1 = fac.generateSecret (dks1); |
|
295 SecretKey key2 = fac.generateSecret (dks2); |
|
296 cipher.init (Cipher.ENCRYPT_MODE, key1); |
|
297 byte[] out1 = cipher.doFinal (magic, 0, 8); |
|
298 cipher.init (Cipher.ENCRYPT_MODE, key2); |
|
299 byte[] out2 = cipher.doFinal (magic, 0, 8); |
|
300 byte[] result = new byte [21]; |
|
301 System.arraycopy (out1, 0, result, 0, 8); |
|
302 System.arraycopy (out2, 0, result, 8, 8); |
|
303 return result; |
|
304 } catch (InvalidKeyException ive) { |
|
305 // Will not happen, all key material are 8 bytes |
|
306 assert false; |
|
307 } catch (InvalidKeySpecException ikse) { |
|
308 // Will not happen, we only feed DESKeySpec to DES factory |
|
309 assert false; |
|
310 } catch (IllegalBlockSizeException ibse) { |
|
311 // Will not happen, we encrypt 8 bytes |
|
312 assert false; |
|
313 } catch (BadPaddingException bpe) { |
|
314 // Will not happen, this is encryption |
|
315 assert false; |
|
316 } |
|
317 return null; // will not happen, we returned already |
|
318 } |
|
319 |
|
320 byte[] calcNTHash (byte[] pw) { |
|
321 byte[] out = md4.digest (pw); |
|
322 byte[] result = new byte [21]; |
|
323 System.arraycopy (out, 0, result, 0, 16); |
|
324 return result; |
|
325 } |
|
326 |
|
327 /* key is a 21 byte array. Split it into 3 7 byte chunks, |
|
328 * Convert each to 8 byte DES keys, encrypt the text arg with |
|
329 * each key and return the three results in a sequential [] |
|
330 */ |
|
331 byte[] calcResponse (byte[] key, byte[] text) { |
|
332 try { |
|
333 assert key.length == 21; |
|
334 DESKeySpec dks1 = new DESKeySpec(makeDesKey(key, 0)); |
|
335 DESKeySpec dks2 = new DESKeySpec(makeDesKey(key, 7)); |
|
336 DESKeySpec dks3 = new DESKeySpec(makeDesKey(key, 14)); |
|
337 SecretKey key1 = fac.generateSecret(dks1); |
|
338 SecretKey key2 = fac.generateSecret(dks2); |
|
339 SecretKey key3 = fac.generateSecret(dks3); |
|
340 cipher.init(Cipher.ENCRYPT_MODE, key1); |
|
341 byte[] out1 = cipher.doFinal(text, 0, 8); |
|
342 cipher.init(Cipher.ENCRYPT_MODE, key2); |
|
343 byte[] out2 = cipher.doFinal(text, 0, 8); |
|
344 cipher.init(Cipher.ENCRYPT_MODE, key3); |
|
345 byte[] out3 = cipher.doFinal(text, 0, 8); |
|
346 byte[] result = new byte[24]; |
|
347 System.arraycopy(out1, 0, result, 0, 8); |
|
348 System.arraycopy(out2, 0, result, 8, 8); |
|
349 System.arraycopy(out3, 0, result, 16, 8); |
|
350 return result; |
|
351 } catch (IllegalBlockSizeException ex) { // None will happen |
|
352 assert false; |
|
353 } catch (BadPaddingException ex) { |
|
354 assert false; |
|
355 } catch (InvalidKeySpecException ex) { |
|
356 assert false; |
|
357 } catch (InvalidKeyException ex) { |
|
358 assert false; |
|
359 } |
|
360 return null; |
|
361 } |
|
362 |
|
363 // LMv2/NTLMv2 |
|
364 |
|
365 byte[] hmacMD5(byte[] key, byte[] text) { |
|
366 try { |
|
367 SecretKeySpec skey = |
|
368 new SecretKeySpec(Arrays.copyOf(key, 16), "HmacMD5"); |
|
369 hmac.init(skey); |
|
370 return hmac.doFinal(text); |
|
371 } catch (InvalidKeyException ex) { |
|
372 assert false; |
|
373 } catch (RuntimeException e) { |
|
374 assert false; |
|
375 } |
|
376 return null; |
|
377 } |
|
378 |
|
379 byte[] calcV2(byte[] nthash, String text, byte[] blob, byte[] challenge) { |
|
380 try { |
|
381 byte[] ntlmv2hash = hmacMD5(nthash, |
|
382 text.getBytes("UnicodeLittleUnmarked")); |
|
383 byte[] cn = new byte[blob.length+8]; |
|
384 System.arraycopy(challenge, 0, cn, 0, 8); |
|
385 System.arraycopy(blob, 0, cn, 8, blob.length); |
|
386 byte[] result = new byte[16+blob.length]; |
|
387 System.arraycopy(hmacMD5(ntlmv2hash, cn), 0, result, 0, 16); |
|
388 System.arraycopy(blob, 0, result, 16, blob.length); |
|
389 return result; |
|
390 } catch (UnsupportedEncodingException ex) { |
|
391 assert false; |
|
392 } |
|
393 return null; |
|
394 } |
|
395 |
|
396 // NTLM2 LM/NTLM |
|
397 |
|
398 static byte[] ntlm2LM(byte[] nonce) { |
|
399 return Arrays.copyOf(nonce, 24); |
|
400 } |
|
401 |
|
402 byte[] ntlm2NTLM(byte[] ntlmHash, byte[] nonce, byte[] challenge) { |
|
403 byte[] b = Arrays.copyOf(challenge, 16); |
|
404 System.arraycopy(nonce, 0, b, 8, 8); |
|
405 byte[] sesshash = Arrays.copyOf(md5.digest(b), 8); |
|
406 return calcResponse(ntlmHash, sesshash); |
|
407 } |
|
408 |
|
409 // Password in ASCII and UNICODE |
|
410 |
|
411 static byte[] getP1(char[] password) { |
|
412 try { |
|
413 return new String(password).toUpperCase().getBytes("ISO8859_1"); |
|
414 } catch (UnsupportedEncodingException ex) { |
|
415 return null; |
|
416 } |
|
417 } |
|
418 |
|
419 static byte[] getP2(char[] password) { |
|
420 try { |
|
421 return new String(password).getBytes("UnicodeLittleUnmarked"); |
|
422 } catch (UnsupportedEncodingException ex) { |
|
423 return null; |
|
424 } |
|
425 } |
|
426 } |