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