View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.mina.proxy.handlers.http.ntlm;
21  
22  import java.io.BufferedReader;
23  import java.io.ByteArrayOutputStream;
24  import java.io.IOException;
25  import java.io.InputStreamReader;
26  import java.io.PrintWriter;
27  import java.io.UnsupportedEncodingException;
28  import java.util.StringTokenizer;
29  
30  import org.apache.mina.proxy.utils.ByteUtilities;
31  
32  /**
33   * NTLMUtilities.java - NTLM functions used for authentication and unit testing.
34   * 
35   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
36   * @since MINA 2.0.0-M3
37   */
38  public class NTLMUtilities implements NTLMConstants {
39      private NTLMUtilities() {
40      }
41      
42      /**
43       * @see #writeSecurityBuffer(short, short, int, byte[], int)
44       * 
45       * @param length The length of the security buffer
46       * @param bufferOffset The offset in the security buffer
47       * @return Th created buffer
48       */
49      public static final byte[] writeSecurityBuffer(short length, int bufferOffset) {
50          byte[] b = new byte[8];
51          writeSecurityBuffer(length, length, bufferOffset, b, 0);
52  
53          return b;
54      }
55  
56      /**
57       * Writes a security buffer to the given array <code>b</code> at offset
58       * <code>offset</code>. A security buffer defines a pointer to an area
59       * in the data that defines some data with a variable length. This allows
60       * to have a semi-fixed length header thus making a little bit easier
61       * the decoding process in the NTLM protocol.
62       * 
63       * @param length the length of the security buffer
64       * @param allocated the allocated space for the security buffer (should be
65       * greater or equal to <code>length</code>
66       * @param bufferOffset the offset from the main array where the currently
67       * defined security buffer will be written
68       * @param b the buffer in which we write the security buffer
69       * @param offset the offset at which to write to the b buffer
70       */
71      public static final void writeSecurityBuffer(short length, short allocated, int bufferOffset, byte[] b, int offset) {
72          ByteUtilities.writeShort(length, b, offset);
73          ByteUtilities.writeShort(allocated, b, offset + 2);
74          ByteUtilities.writeInt(bufferOffset, b, offset + 4);
75      }
76  
77      /**
78       * Writes the Windows OS version passed in as three byte values
79       * (majorVersion.minorVersion.buildNumber) to the given byte array
80       * at <code>offset</code>.
81       * 
82       * @param majorVersion the major version number
83       * @param minorVersion the minor version number
84       * @param buildNumber the build number
85       * @param b the target byte array
86       * @param offset the offset at which to write in the array
87       */
88      public static final void writeOSVersion(byte majorVersion, byte minorVersion, short buildNumber, byte[] b,
89              int offset) {
90          b[offset] = majorVersion;
91          b[offset + 1] = minorVersion;
92          b[offset + 2] = (byte) buildNumber;
93          b[offset + 3] = (byte) (buildNumber >> 8);
94          b[offset + 4] = 0;
95          b[offset + 5] = 0;
96          b[offset + 6] = 0;
97          b[offset + 7] = 0x0F;
98      }
99  
100     /**
101      * Tries to return a valid OS version on Windows systems. If it fails to
102      * do so or if we're running on another OS then a fake Windows XP OS
103      * version is returned because the protocol uses it.
104      * 
105      * @return a NTLM OS version byte buffer
106      */
107     public static final byte[] getOsVersion() {
108         String os = System.getProperty("os.name");
109 
110         if ((os == null) || !os.toUpperCase().contains("WINDOWS")) {
111             return DEFAULT_OS_VERSION;
112         }
113 
114         byte[] osVer = new byte[8];
115 
116         // Let's enclose the code by a try...catch in order to
117         // manage incorrect strings. In this case, we will generate
118         // an exception and deal with the special cases.
119         try {
120             Process pr = Runtime.getRuntime().exec("cmd /C ver");
121             BufferedReader reader = new BufferedReader(new InputStreamReader(pr.getInputStream()));
122             pr.waitFor();
123 
124             String line;
125 
126             // We loop as we may have blank lines.
127             do {
128                 line = reader.readLine();
129             } while ((line != null) && (line.length() != 0));
130 
131             reader.close();
132 
133             // If line is null, we must not go any farther
134             if (line == null) {
135                 // Throw an exception to jump into the catch() part
136                 throw new Exception();
137             }
138 
139             // The command line should return a response like :
140             // Microsoft Windows XP [version 5.1.2600]
141             int pos = line.toLowerCase().indexOf("version");
142 
143             if (pos == -1) {
144                 // Throw an exception to jump into the catch() part
145                 throw new Exception();
146             }
147 
148             pos += 8;
149             line = line.substring(pos, line.indexOf(']'));
150             StringTokenizer tk = new StringTokenizer(line, ".");
151 
152             if (tk.countTokens() != 3) {
153                 // Throw an exception to jump into the catch() part
154                 throw new Exception();
155             }
156 
157             writeOSVersion(Byte.parseByte(tk.nextToken()), Byte.parseByte(tk.nextToken()),
158                     Short.parseShort(tk.nextToken()), osVer, 0);
159         } catch (Exception ex) {
160             try {
161                 String version = System.getProperty("os.version");
162                 writeOSVersion(Byte.parseByte(version.substring(0, 1)), Byte.parseByte(version.substring(2, 3)),
163                         (short) 0, osVer, 0);
164             } catch (Exception ex2) {
165                 return DEFAULT_OS_VERSION;
166             }
167         }
168 
169         return osVer;
170     }
171 
172     /**
173      * see http://davenport.sourceforge.net/ntlm.html#theType1Message
174      * 
175      * @param workStation the workstation name
176      * @param domain the domain name
177      * @param customFlags custom flags, if null then
178      * <code>NTLMConstants.DEFAULT_CONSTANTS</code> is used
179      * @param osVersion the os version of the client, if null then
180      * <code>NTLMConstants.DEFAULT_OS_VERSION</code> is used
181      * @return the type 1 message
182      */
183     public static final byte[] createType1Message(String workStation, String domain, Integer customFlags,
184             byte[] osVersion) {
185         byte[] msg;
186 
187         if (osVersion != null && osVersion.length != 8) {
188             throw new IllegalArgumentException("osVersion parameter should be a 8 byte wide array");
189         }
190 
191         if (workStation == null || domain == null) {
192             throw new IllegalArgumentException("workStation and domain must be non null");
193         }
194 
195         int flags = customFlags != null ? customFlags | FLAG_NEGOTIATE_WORKSTATION_SUPPLIED
196                 | FLAG_NEGOTIATE_DOMAIN_SUPPLIED : DEFAULT_FLAGS;
197 
198         ByteArrayOutputStream baos = new ByteArrayOutputStream();
199 
200         try {
201             baos.write(NTLM_SIGNATURE);
202             baos.write(ByteUtilities.writeInt(MESSAGE_TYPE_1));
203             baos.write(ByteUtilities.writeInt(flags));
204 
205             byte[] domainData = ByteUtilities.getOEMStringAsByteArray(domain);
206             byte[] workStationData = ByteUtilities.getOEMStringAsByteArray(workStation);
207 
208             int pos = (osVersion != null) ? 40 : 32;
209             baos.write(writeSecurityBuffer((short) domainData.length, pos + workStationData.length));
210             baos.write(writeSecurityBuffer((short) workStationData.length, pos));
211 
212             if (osVersion != null) {
213                 baos.write(osVersion);
214             }
215 
216             // Order is not mandatory since a pointer is given in the security buffers
217             baos.write(workStationData);
218             baos.write(domainData);
219 
220             msg = baos.toByteArray();
221             baos.close();
222         } catch (IOException e) {
223             return null;
224         }
225 
226         return msg;
227     }
228 
229     /**
230      * Writes a security buffer and returns the pointer of the position
231      * where to write the next security buffer.
232      * 
233      * @param baos the stream where the security buffer is written
234      * @param len the length of the security buffer
235      * @param pointer the position where the security buffer can be written
236      * @return the position where the next security buffer will be written
237      * @throws IOException if writing to the ByteArrayOutputStream fails
238      */
239     public static final int writeSecurityBufferAndUpdatePointer(ByteArrayOutputStream baos, short len, int pointer)
240             throws IOException {
241         baos.write(writeSecurityBuffer(len, pointer));
242 
243         return pointer + len;
244     }
245 
246     /**
247      * Extracts the NTLM challenge from the type 2 message as an 8 byte array.
248      * 
249      * @param msg the type 2 message byte array
250      * @return the challenge
251      */
252     public static final byte[] extractChallengeFromType2Message(byte[] msg) {
253         byte[] challenge = new byte[8];
254         System.arraycopy(msg, 24, challenge, 0, 8);
255 
256         return challenge;
257     }
258 
259     /**
260      * Extracts the NTLM flags from the type 2 message.
261      * 
262      * @param msg the type 2 message byte array
263      * @return the proxy flags as an int
264      */
265     public static final int extractFlagsFromType2Message(byte[] msg) {
266         byte[] flagsBytes = new byte[4];
267 
268         System.arraycopy(msg, 20, flagsBytes, 0, 4);
269         ByteUtilities.changeWordEndianess(flagsBytes, 0, 4);
270 
271         return ByteUtilities.makeIntFromByte4(flagsBytes);
272     }
273 
274     /**
275      * Reads the byte array described by the security buffer stored at the
276      * <code>securityBufferOffset</code> offset.
277      * 
278      * @param msg the message where to read the security buffer and it's value
279      * @param securityBufferOffset the offset at which to read the security buffer
280      * @return a new byte array holding the data pointed by the security buffer
281      */
282     public static final byte[] readSecurityBufferTarget(byte[] msg, int securityBufferOffset) {
283         byte[] securityBuffer = new byte[8];
284 
285         System.arraycopy(msg, securityBufferOffset, securityBuffer, 0, 8);
286         ByteUtilities.changeWordEndianess(securityBuffer, 0, 8);
287         int length = ByteUtilities.makeIntFromByte2(securityBuffer);
288         int offset = ByteUtilities.makeIntFromByte4(securityBuffer, 4);
289 
290         byte[] secBufValue = new byte[length];
291         System.arraycopy(msg, offset, secBufValue, 0, length);
292 
293         return secBufValue;
294     }
295 
296     /**
297      * Extracts the target name from the type 2 message.
298      * 
299      * @param msg the type 2 message byte array
300      * @param msgFlags the flags if null then flags are extracted from the
301      * type 2 message
302      * @return the target name
303      * @throws UnsupportedEncodingException if unable to use the
304      * needed UTF-16LE or ASCII charsets
305      */
306     public static final String extractTargetNameFromType2Message(byte[] msg, Integer msgFlags)
307             throws UnsupportedEncodingException {
308         // Read the security buffer to determine where the target name
309         // is stored and what it's length is
310         byte[] targetName = readSecurityBufferTarget(msg, 12);
311 
312         // now we convert it to a string
313         int flags = msgFlags == null ? extractFlagsFromType2Message(msg) : msgFlags;
314 
315         if (ByteUtilities.isFlagSet(flags, FLAG_NEGOTIATE_UNICODE)) {
316             return new String(targetName, "UTF-16LE");
317         }
318 
319         return new String(targetName, "ASCII");
320     }
321 
322     /**
323      * Extracts the target information block from the type 2 message.
324      * 
325      * @param msg the type 2 message byte array
326      * @param msgFlags the flags if null then flags are extracted from the
327      * type 2 message
328      * @return the target info
329      */
330     public static final byte[] extractTargetInfoFromType2Message(byte[] msg, Integer msgFlags) {
331         int flags = msgFlags == null ? extractFlagsFromType2Message(msg) : msgFlags;
332 
333         if (!ByteUtilities.isFlagSet(flags, FLAG_NEGOTIATE_TARGET_INFO)) {
334             return null;
335         }
336 
337         int pos = 40;
338 
339         return readSecurityBufferTarget(msg, pos);
340     }
341 
342     /**
343      * Prints to the {@link PrintWriter} the target information block extracted
344      * from the type 2 message.
345      * 
346      * @param msg the type 2 message
347      * @param msgFlags the flags if null then flags are extracted from the
348      * type 2 message
349      * @param out the output target for the information
350      * @throws UnsupportedEncodingException if unable to use the
351      * needed UTF-16LE or ASCII charsets
352      */
353     public static final void printTargetInformationBlockFromType2Message(byte[] msg, Integer msgFlags, PrintWriter out)
354             throws UnsupportedEncodingException {
355         int flags = msgFlags == null ? extractFlagsFromType2Message(msg) : msgFlags;
356 
357         byte[] infoBlock = extractTargetInfoFromType2Message(msg, flags);
358 
359         if (infoBlock == null) {
360             out.println("No target information block found !");
361         } else {
362             int pos = 0;
363 
364             while (infoBlock[pos] != 0) {
365                 out.print("---\nType " + infoBlock[pos] + ": ");
366 
367                 switch (infoBlock[pos]) {
368                 case 1:
369                     out.println("Server name");
370                     break;
371                 case 2:
372                     out.println("Domain name");
373                     break;
374                 case 3:
375                     out.println("Fully qualified DNS hostname");
376                     break;
377                 case 4:
378                     out.println("DNS domain name");
379                     break;
380                 case 5:
381                     out.println("Parent DNS domain name");
382                     break;
383                 }
384 
385                 byte[] len = new byte[2];
386                 System.arraycopy(infoBlock, pos + 2, len, 0, 2);
387                 ByteUtilities.changeByteEndianess(len, 0, 2);
388 
389                 int length = ByteUtilities.makeIntFromByte2(len, 0);
390                 out.println("Length: " + length + " bytes");
391                 out.print("Data: ");
392 
393                 if (ByteUtilities.isFlagSet(flags, FLAG_NEGOTIATE_UNICODE)) {
394                     out.println(new String(infoBlock, pos + 4, length, "UTF-16LE"));
395                 } else {
396                     out.println(new String(infoBlock, pos + 4, length, "ASCII"));
397                 }
398 
399                 pos += 4 + length;
400                 out.flush();
401             }
402         }
403     }
404 
405     /**
406      * @see <a href="http://davenport.sourceforge.net/ntlm.html#theType3Message">NTLM message type</a>
407      * 
408      * @param user the user name
409      * @param password the user password
410      * @param challenge the challenge response
411      * @param target the target name
412      * @param workstation the client workstation's name
413      * @param serverFlags the flags set by the client
414      * @param osVersion the os version of the client
415      * @return the type 3 message
416      */
417     public static final byte[] createType3Message(String user, String password, byte[] challenge, String target,
418             String workstation, Integer serverFlags, byte[] osVersion) {
419         byte[] msg;
420 
421         if (challenge == null || challenge.length != 8) {
422             throw new IllegalArgumentException("challenge[] should be a 8 byte wide array");
423         }
424 
425         if (osVersion != null && osVersion.length != 8) {
426             throw new IllegalArgumentException("osVersion should be a 8 byte wide array");
427         }
428 
429         int flags = serverFlags != null ? serverFlags : DEFAULT_FLAGS;
430 
431         ByteArrayOutputStream baos = new ByteArrayOutputStream();
432 
433         try {
434             baos.write(NTLM_SIGNATURE);
435             baos.write(ByteUtilities.writeInt(MESSAGE_TYPE_3));
436 
437             byte[] dataLMResponse = NTLMResponses.getLMResponse(password, challenge);
438             byte[] dataNTLMResponse = NTLMResponses.getNTLMResponse(password, challenge);
439 
440             boolean useUnicode = ByteUtilities.isFlagSet(flags, FLAG_NEGOTIATE_UNICODE);
441             byte[] targetName = ByteUtilities.encodeString(target, useUnicode);
442             byte[] userName = ByteUtilities.encodeString(user, useUnicode);
443             byte[] workstationName = ByteUtilities.encodeString(workstation, useUnicode);
444 
445             int pos = osVersion != null ? 72 : 64;
446             int responsePos = pos + targetName.length + userName.length + workstationName.length;
447             responsePos = writeSecurityBufferAndUpdatePointer(baos, (short) dataLMResponse.length, responsePos);
448             writeSecurityBufferAndUpdatePointer(baos, (short) dataNTLMResponse.length, responsePos);
449             pos = writeSecurityBufferAndUpdatePointer(baos, (short) targetName.length, pos);
450             pos = writeSecurityBufferAndUpdatePointer(baos, (short) userName.length, pos);
451             writeSecurityBufferAndUpdatePointer(baos, (short) workstationName.length, pos);
452 
453             /**
454             LM/LMv2 Response security buffer
455             20 NTLM/NTLMv2 Response security buffer
456             28 Target Name security buffer
457             36 User Name security buffer
458             44 Workstation Name security buffer
459             (52) Session Key (optional) security buffer
460             (60) Flags (optional) long
461             (64) OS Version Structure (Optional) 8 bytes
462              **/
463 
464             // Session Key Security Buffer ??!
465             baos.write(new byte[] { 0, 0, 0, 0, (byte) 0x9a, 0, 0, 0 });
466 
467             baos.write(ByteUtilities.writeInt(flags));
468 
469             if (osVersion != null) {
470                 baos.write(osVersion);
471             }
472 
473             // Order is not mandatory since a pointer is given in the security buffers
474             baos.write(targetName);
475             baos.write(userName);
476             baos.write(workstationName);
477 
478             baos.write(dataLMResponse);
479             baos.write(dataNTLMResponse);
480 
481             msg = baos.toByteArray();
482             baos.close();
483         } catch (Exception e) {
484             e.printStackTrace();
485             return null;
486         }
487 
488         return msg;
489     }
490 }