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             String line;
122 
123             try (BufferedReader reader = new BufferedReader(new InputStreamReader(pr.getInputStream()))) {
124 	            pr.waitFor();
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 
132             // If line is null, we must not go any farther
133             if (line == null) {
134                 // Throw an exception to jump into the catch() part
135                 throw new Exception();
136             }
137 
138             // The command line should return a response like :
139             // Microsoft Windows XP [version 5.1.2600]
140             int pos = line.toLowerCase().indexOf("version");
141 
142             if (pos == -1) {
143                 // Throw an exception to jump into the catch() part
144                 throw new Exception();
145             }
146 
147             pos += 8;
148             line = line.substring(pos, line.indexOf(']'));
149             StringTokenizer tk = new StringTokenizer(line, ".");
150 
151             if (tk.countTokens() != 3) {
152                 // Throw an exception to jump into the catch() part
153                 throw new Exception();
154             }
155 
156             writeOSVersion(Byte.parseByte(tk.nextToken()), Byte.parseByte(tk.nextToken()),
157                     Short.parseShort(tk.nextToken()), osVer, 0);
158         } catch (Exception ex) {
159             try {
160                 String version = System.getProperty("os.version");
161                 writeOSVersion(Byte.parseByte(version.substring(0, 1)), Byte.parseByte(version.substring(2, 3)),
162                         (short) 0, osVer, 0);
163             } catch (Exception ex2) {
164                 return DEFAULT_OS_VERSION;
165             }
166         }
167 
168         return osVer;
169     }
170 
171     /**
172      * see http://davenport.sourceforge.net/ntlm.html#theType1Message
173      * 
174      * @param workStation the workstation name
175      * @param domain the domain name
176      * @param customFlags custom flags, if null then
177      * <code>NTLMConstants.DEFAULT_CONSTANTS</code> is used
178      * @param osVersion the os version of the client, if null then
179      * <code>NTLMConstants.DEFAULT_OS_VERSION</code> is used
180      * @return the type 1 message
181      */
182     public static final byte[] createType1Message(String workStation, String domain, Integer customFlags,
183             byte[] osVersion) {
184         byte[] msg;
185 
186         if (osVersion != null && osVersion.length != 8) {
187             throw new IllegalArgumentException("osVersion parameter should be a 8 byte wide array");
188         }
189 
190         if (workStation == null || domain == null) {
191             throw new IllegalArgumentException("workStation and domain must be non null");
192         }
193 
194         int flags = customFlags != null ? customFlags | FLAG_NEGOTIATE_WORKSTATION_SUPPLIED
195                 | FLAG_NEGOTIATE_DOMAIN_SUPPLIED : DEFAULT_FLAGS;
196 
197         ByteArrayOutputStream baos = new ByteArrayOutputStream();
198 
199         try {
200             baos.write(NTLM_SIGNATURE);
201             baos.write(ByteUtilities.writeInt(MESSAGE_TYPE_1));
202             baos.write(ByteUtilities.writeInt(flags));
203 
204             byte[] domainData = ByteUtilities.getOEMStringAsByteArray(domain);
205             byte[] workStationData = ByteUtilities.getOEMStringAsByteArray(workStation);
206 
207             int pos = (osVersion != null) ? 40 : 32;
208             baos.write(writeSecurityBuffer((short) domainData.length, pos + workStationData.length));
209             baos.write(writeSecurityBuffer((short) workStationData.length, pos));
210 
211             if (osVersion != null) {
212                 baos.write(osVersion);
213             }
214 
215             // Order is not mandatory since a pointer is given in the security buffers
216             baos.write(workStationData);
217             baos.write(domainData);
218 
219             msg = baos.toByteArray();
220             baos.close();
221         } catch (IOException e) {
222             return null;
223         }
224 
225         return msg;
226     }
227 
228     /**
229      * Writes a security buffer and returns the pointer of the position
230      * where to write the next security buffer.
231      * 
232      * @param baos the stream where the security buffer is written
233      * @param len the length of the security buffer
234      * @param pointer the position where the security buffer can be written
235      * @return the position where the next security buffer will be written
236      * @throws IOException if writing to the ByteArrayOutputStream fails
237      */
238     public static final int writeSecurityBufferAndUpdatePointer(ByteArrayOutputStream baos, short len, int pointer)
239             throws IOException {
240         baos.write(writeSecurityBuffer(len, pointer));
241 
242         return pointer + len;
243     }
244 
245     /**
246      * Extracts the NTLM challenge from the type 2 message as an 8 byte array.
247      * 
248      * @param msg the type 2 message byte array
249      * @return the challenge
250      */
251     public static final byte[] extractChallengeFromType2Message(byte[] msg) {
252         byte[] challenge = new byte[8];
253         System.arraycopy(msg, 24, challenge, 0, 8);
254 
255         return challenge;
256     }
257 
258     /**
259      * Extracts the NTLM flags from the type 2 message.
260      * 
261      * @param msg the type 2 message byte array
262      * @return the proxy flags as an int
263      */
264     public static final int extractFlagsFromType2Message(byte[] msg) {
265         byte[] flagsBytes = new byte[4];
266 
267         System.arraycopy(msg, 20, flagsBytes, 0, 4);
268         ByteUtilities.changeWordEndianess(flagsBytes, 0, 4);
269 
270         return ByteUtilities.makeIntFromByte4(flagsBytes);
271     }
272 
273     /**
274      * Reads the byte array described by the security buffer stored at the
275      * <code>securityBufferOffset</code> offset.
276      * 
277      * @param msg the message where to read the security buffer and it's value
278      * @param securityBufferOffset the offset at which to read the security buffer
279      * @return a new byte array holding the data pointed by the security buffer
280      */
281     public static final byte[] readSecurityBufferTarget(byte[] msg, int securityBufferOffset) {
282         byte[] securityBuffer = new byte[8];
283 
284         System.arraycopy(msg, securityBufferOffset, securityBuffer, 0, 8);
285         ByteUtilities.changeWordEndianess(securityBuffer, 0, 8);
286         int length = ByteUtilities.makeIntFromByte2(securityBuffer);
287         int offset = ByteUtilities.makeIntFromByte4(securityBuffer, 4);
288 
289         byte[] secBufValue = new byte[length];
290         System.arraycopy(msg, offset, secBufValue, 0, length);
291 
292         return secBufValue;
293     }
294 
295     /**
296      * Extracts the target name from the type 2 message.
297      * 
298      * @param msg the type 2 message byte array
299      * @param msgFlags the flags if null then flags are extracted from the
300      * type 2 message
301      * @return the target name
302      * @throws UnsupportedEncodingException if unable to use the
303      * needed UTF-16LE or ASCII charsets
304      */
305     public static final String extractTargetNameFromType2Message(byte[] msg, Integer msgFlags)
306             throws UnsupportedEncodingException {
307         // Read the security buffer to determine where the target name
308         // is stored and what it's length is
309         byte[] targetName = readSecurityBufferTarget(msg, 12);
310 
311         // now we convert it to a string
312         int flags = msgFlags == null ? extractFlagsFromType2Message(msg) : msgFlags;
313 
314         if (ByteUtilities.isFlagSet(flags, FLAG_NEGOTIATE_UNICODE)) {
315             return new String(targetName, "UTF-16LE");
316         }
317 
318         return new String(targetName, "ASCII");
319     }
320 
321     /**
322      * Extracts the target information block from the type 2 message.
323      * 
324      * @param msg the type 2 message byte array
325      * @param msgFlags the flags if null then flags are extracted from the
326      * type 2 message
327      * @return the target info
328      */
329     public static final byte[] extractTargetInfoFromType2Message(byte[] msg, Integer msgFlags) {
330         int flags = msgFlags == null ? extractFlagsFromType2Message(msg) : msgFlags;
331 
332         if (!ByteUtilities.isFlagSet(flags, FLAG_NEGOTIATE_TARGET_INFO)) {
333             return null;
334         }
335 
336         int pos = 40;
337 
338         return readSecurityBufferTarget(msg, pos);
339     }
340 
341     /**
342      * Prints to the {@link PrintWriter} the target information block extracted
343      * from the type 2 message.
344      * 
345      * @param msg the type 2 message
346      * @param msgFlags the flags if null then flags are extracted from the
347      * type 2 message
348      * @param out the output target for the information
349      * @throws UnsupportedEncodingException if unable to use the
350      * needed UTF-16LE or ASCII charsets
351      */
352     public static final void printTargetInformationBlockFromType2Message(byte[] msg, Integer msgFlags, PrintWriter out)
353             throws UnsupportedEncodingException {
354         int flags = msgFlags == null ? extractFlagsFromType2Message(msg) : msgFlags;
355 
356         byte[] infoBlock = extractTargetInfoFromType2Message(msg, flags);
357 
358         if (infoBlock == null) {
359             out.println("No target information block found !");
360         } else {
361             int pos = 0;
362 
363             while (infoBlock[pos] != 0) {
364                 out.print("---\nType " + infoBlock[pos] + ": ");
365 
366                 switch (infoBlock[pos]) {
367                 case 1:
368                     out.println("Server name");
369                     break;
370                 case 2:
371                     out.println("Domain name");
372                     break;
373                 case 3:
374                     out.println("Fully qualified DNS hostname");
375                     break;
376                 case 4:
377                     out.println("DNS domain name");
378                     break;
379                 case 5:
380                     out.println("Parent DNS domain name");
381                     break;
382                 }
383 
384                 byte[] len = new byte[2];
385                 System.arraycopy(infoBlock, pos + 2, len, 0, 2);
386                 ByteUtilities.changeByteEndianess(len, 0, 2);
387 
388                 int length = ByteUtilities.makeIntFromByte2(len, 0);
389                 out.println("Length: " + length + " bytes");
390                 out.print("Data: ");
391 
392                 if (ByteUtilities.isFlagSet(flags, FLAG_NEGOTIATE_UNICODE)) {
393                     out.println(new String(infoBlock, pos + 4, length, "UTF-16LE"));
394                 } else {
395                     out.println(new String(infoBlock, pos + 4, length, "ASCII"));
396                 }
397 
398                 pos += 4 + length;
399                 out.flush();
400             }
401         }
402     }
403 
404     /**
405      * @see <a href="http://davenport.sourceforge.net/ntlm.html#theType3Message">NTLM message type</a>
406      * 
407      * @param user the user name
408      * @param password the user password
409      * @param challenge the challenge response
410      * @param target the target name
411      * @param workstation the client workstation's name
412      * @param serverFlags the flags set by the client
413      * @param osVersion the os version of the client
414      * @return the type 3 message
415      */
416     public static final byte[] createType3Message(String user, String password, byte[] challenge, String target,
417             String workstation, Integer serverFlags, byte[] osVersion) {
418         byte[] msg;
419 
420         if (challenge == null || challenge.length != 8) {
421             throw new IllegalArgumentException("challenge[] should be a 8 byte wide array");
422         }
423 
424         if (osVersion != null && osVersion.length != 8) {
425             throw new IllegalArgumentException("osVersion should be a 8 byte wide array");
426         }
427 
428         int flags = serverFlags != null ? serverFlags : DEFAULT_FLAGS;
429 
430         ByteArrayOutputStream baos = new ByteArrayOutputStream();
431 
432         try {
433             baos.write(NTLM_SIGNATURE);
434             baos.write(ByteUtilities.writeInt(MESSAGE_TYPE_3));
435 
436             byte[] dataLMResponse = NTLMResponses.getLMResponse(password, challenge);
437             byte[] dataNTLMResponse = NTLMResponses.getNTLMResponse(password, challenge);
438 
439             boolean useUnicode = ByteUtilities.isFlagSet(flags, FLAG_NEGOTIATE_UNICODE);
440             byte[] targetName = ByteUtilities.encodeString(target, useUnicode);
441             byte[] userName = ByteUtilities.encodeString(user, useUnicode);
442             byte[] workstationName = ByteUtilities.encodeString(workstation, useUnicode);
443 
444             int pos = osVersion != null ? 72 : 64;
445             int responsePos = pos + targetName.length + userName.length + workstationName.length;
446             responsePos = writeSecurityBufferAndUpdatePointer(baos, (short) dataLMResponse.length, responsePos);
447             writeSecurityBufferAndUpdatePointer(baos, (short) dataNTLMResponse.length, responsePos);
448             pos = writeSecurityBufferAndUpdatePointer(baos, (short) targetName.length, pos);
449             pos = writeSecurityBufferAndUpdatePointer(baos, (short) userName.length, pos);
450             writeSecurityBufferAndUpdatePointer(baos, (short) workstationName.length, pos);
451 
452             /**
453             LM/LMv2 Response security buffer
454             20 NTLM/NTLMv2 Response security buffer
455             28 Target Name security buffer
456             36 User Name security buffer
457             44 Workstation Name security buffer
458             (52) Session Key (optional) security buffer
459             (60) Flags (optional) long
460             (64) OS Version Structure (Optional) 8 bytes
461              **/
462 
463             // Session Key Security Buffer ??!
464             baos.write(new byte[] { 0, 0, 0, 0, (byte) 0x9a, 0, 0, 0 });
465 
466             baos.write(ByteUtilities.writeInt(flags));
467 
468             if (osVersion != null) {
469                 baos.write(osVersion);
470             }
471 
472             // Order is not mandatory since a pointer is given in the security buffers
473             baos.write(targetName);
474             baos.write(userName);
475             baos.write(workstationName);
476 
477             baos.write(dataLMResponse);
478             baos.write(dataNTLMResponse);
479 
480             msg = baos.toByteArray();
481             baos.close();
482         } catch (Exception e) {
483             e.printStackTrace();
484             return null;
485         }
486 
487         return msg;
488     }
489 }