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