View Javadoc
1 /* 2 * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/Attic/NTLM.java,v 1.12.2.1 2003/08/16 00:09:37 adrian Exp $ 3 * $Revision: 1.12.2.1 $ 4 * $Date: 2003/08/16 00:09:37 $ 5 * 6 * ==================================================================== 7 * 8 * The Apache Software License, Version 1.1 9 * 10 * Copyright (c) 2002-2003 The Apache Software Foundation. All rights 11 * reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 17 * 1. Redistributions of source code must retain the above copyright 18 * notice, this list of conditions and the following disclaimer. 19 * 20 * 2. Redistributions in binary form must reproduce the above copyright 21 * notice, this list of conditions and the following disclaimer in 22 * the documentation and/or other materials provided with the 23 * distribution. 24 * 25 * 3. The end-user documentation included with the redistribution, if 26 * any, must include the following acknowlegement: 27 * "This product includes software developed by the 28 * Apache Software Foundation (http://www.apache.org/)." 29 * Alternately, this acknowlegement may appear in the software itself, 30 * if and wherever such third-party acknowlegements normally appear. 31 * 32 * 4. The names "The Jakarta Project", "Commons", and "Apache Software 33 * Foundation" must not be used to endorse or promote products derived 34 * from this software without prior written permission. For written 35 * permission, please contact apache@apache.org. 36 * 37 * 5. Products derived from this software may not be called "Apache" 38 * nor may "Apache" appear in their names without prior written 39 * permission of the Apache Group. 40 * 41 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 42 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 43 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 44 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR 45 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 46 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 47 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 48 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 49 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 50 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 51 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 52 * SUCH DAMAGE. 53 * ==================================================================== 54 * 55 * This software consists of voluntary contributions made by many 56 * individuals on behalf of the Apache Software Foundation. For more 57 * information on the Apache Software Foundation, please see 58 * <http://www.apache.org/>. 59 * 60 * [Additional notices, if required by prior licensing conditions] 61 * 62 */ 63 64 package org.apache.commons.httpclient; 65 66 import java.io.UnsupportedEncodingException; 67 import java.security.InvalidKeyException; 68 import java.security.NoSuchAlgorithmException; 69 70 import javax.crypto.BadPaddingException; 71 import javax.crypto.Cipher; 72 import javax.crypto.IllegalBlockSizeException; 73 import javax.crypto.NoSuchPaddingException; 74 import javax.crypto.spec.SecretKeySpec; 75 76 import org.apache.commons.httpclient.util.Base64; 77 import org.apache.commons.logging.Log; 78 import org.apache.commons.logging.LogFactory; 79 80 /*** 81 * Provides an implementation of the NTLM authentication protocol. 82 * <p> 83 * This class provides methods for generating authentication 84 * challenge responses for the NTLM authentication protocol. The NTLM 85 * protocol is a proprietary Microsoft protocol and as such no RFC 86 * exists for it. This class is based upon the reverse engineering 87 * efforts of a wide range of people.</p> 88 * 89 * <p>Please note that an implementation of JCE must be correctly installed and configured when 90 * using NTLM support.</p> 91 * 92 * <p>This class should not be used externally to HttpClient as it's API is specifically 93 * designed to work with HttpClient's use case, in particular it's connection management.</p> 94 * 95 * @deprecated this class will be made package access for 2.0beta2 96 * 97 * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a> 98 * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a> 99 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> 100 * 101 * @version $Revision: 1.12.2.1 $ $Date: 2003/08/16 00:09:37 $ 102 * @since 2.0alpha2 103 */ 104 public final class NTLM { 105 106 /*** The current response */ 107 private byte[] currentResponse; 108 109 /*** The current position */ 110 private int currentPosition = 0; 111 112 /*** Log object for this class. */ 113 private static final Log LOG = LogFactory.getLog(NTLM.class); 114 115 /*** Character encoding */ 116 public static final String DEFAULT_CHARSET = "ASCII"; 117 118 /*** 119 * Returns the response for the given message. 120 * 121 * @param message the message that was received from the server. 122 * @param username the username to authenticate with. 123 * @param password the password to authenticate with. 124 * @param host The host. 125 * @param domain the NT domain to authenticate in. 126 * @return The response. 127 * @throws HttpException If the messages cannot be retrieved. 128 */ 129 public final String getResponseFor(String message, 130 String username, String password, String host, String domain) 131 throws HttpException { 132 133 final String response; 134 if (message == null || message.trim().equals("")) { 135 response = getType1Message(host, domain); 136 } else { 137 response = getType3Message(username, password, host, domain, 138 parseType2Message(message)); 139 } 140 return response; 141 } 142 143 /*** 144 * Return the cipher for the specified key. 145 * @param key The key. 146 * @return Cipher The cipher. 147 * @throws HttpException If the cipher cannot be retrieved. 148 */ 149 private Cipher getCipher(byte[] key) throws HttpException { 150 try { 151 final Cipher ecipher = Cipher.getInstance("DES/ECB/NoPadding"); 152 key = setupKey(key); 153 ecipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "DES")); 154 return ecipher; 155 } catch (NoSuchAlgorithmException e) { 156 throw new HttpException("DES encryption is not available."); 157 } catch (InvalidKeyException e) { 158 throw new HttpException("Invalid key for DES encryption."); 159 } catch (NoSuchPaddingException e) { 160 throw new HttpException( 161 "NoPadding option for DES is not available."); 162 } 163 } 164 165 /*** 166 * Adds parity bits to the key. 167 * @param key56 The key 168 * @return The modified key. 169 */ 170 private byte[] setupKey(byte[] key56) { 171 byte[] key = new byte[8]; 172 key[0] = (byte) ((key56[0] >> 1) & 0xff); 173 key[1] = (byte) ((((key56[0] & 0x01) << 6) 174 | (((key56[1] & 0xff) >> 2) & 0xff)) & 0xff); 175 key[2] = (byte) ((((key56[1] & 0x03) << 5) 176 | (((key56[2] & 0xff) >> 3) & 0xff)) & 0xff); 177 key[3] = (byte) ((((key56[2] & 0x07) << 4) 178 | (((key56[3] & 0xff) >> 4) & 0xff)) & 0xff); 179 key[4] = (byte) ((((key56[3] & 0x0f) << 3) 180 | (((key56[4] & 0xff) >> 5) & 0xff)) & 0xff); 181 key[5] = (byte) ((((key56[4] & 0x1f) << 2) 182 | (((key56[5] & 0xff) >> 6) & 0xff)) & 0xff); 183 key[6] = (byte) ((((key56[5] & 0x3f) << 1) 184 | (((key56[6] & 0xff) >> 7) & 0xff)) & 0xff); 185 key[7] = (byte) (key56[6] & 0x7f); 186 187 for (int i = 0; i < key.length; i++) { 188 key[i] = (byte) (key[i] << 1); 189 } 190 return key; 191 } 192 193 /*** 194 * Encrypt the data. 195 * @param key The key. 196 * @param bytes The data 197 * @return byte[] The encrypted data 198 * @throws HttpException If {@link Cipher.doFinal(byte[])} fails 199 */ 200 private byte[] encrypt(byte[] key, byte[] bytes) 201 throws HttpException { 202 Cipher ecipher = getCipher(key); 203 try { 204 byte[] enc = ecipher.doFinal(bytes); 205 return enc; 206 } catch (IllegalBlockSizeException e) { 207 throw new HttpException("Invalid block size for DES encryption."); 208 } catch (BadPaddingException e) { 209 throw new HttpException( 210 "Data not padded correctly for DES encryption."); 211 } 212 } 213 214 /*** 215 * Prepares the object to create a response of the given length. 216 * @param length the length of the response to prepare. 217 */ 218 private void prepareResponse(int length) { 219 currentResponse = new byte[length]; 220 currentPosition = 0; 221 } 222 223 /*** 224 * Adds the given byte to the response. 225 * @param b the byte to add. 226 */ 227 private void addByte(byte b) { 228 currentResponse[currentPosition] = b; 229 currentPosition++; 230 } 231 232 /*** 233 * Adds the given bytes to the response. 234 * @param bytes the bytes to add. 235 */ 236 private void addBytes(byte[] bytes) { 237 for (int i = 0; i < bytes.length; i++) { 238 currentResponse[currentPosition] = bytes[i]; 239 currentPosition++; 240 } 241 } 242 243 /*** 244 * Returns the response that has been generated after shrinking the array if 245 * required and base64 encodes the response. 246 * @return The response as above. 247 */ 248 private String getResponse() { 249 byte[] resp; 250 if (currentResponse.length > currentPosition) { 251 byte[] tmp = new byte[currentPosition]; 252 for (int i = 0; i < currentPosition; i++) { 253 tmp[i] = currentResponse[i]; 254 } 255 resp = tmp; 256 } else { 257 resp = currentResponse; 258 } 259 return HttpConstants.getString(Base64.encode(resp)); 260 } 261 262 /*** 263 * Creates the first message (type 1 message) in the NTLM authentication sequence. 264 * This message includes the user name, domain and host for the authentication session. 265 * 266 * @param host the computer name of the host requesting authentication. 267 * @param domain The domain to authenticate with. 268 * @return String the message to add to the HTTP request header. 269 */ 270 private String getType1Message(String host, String domain) { 271 host = host.toUpperCase(); 272 domain = domain.toUpperCase(); 273 byte[] hostBytes = getBytes(host); 274 byte[] domainBytes = getBytes(domain); 275 276 int finalLength = 32 + hostBytes.length + domainBytes.length; 277 prepareResponse(finalLength); 278 279 // The initial id string. 280 byte[] protocol = getBytes("NTLMSSP"); 281 addBytes(protocol); 282 addByte((byte) 0); 283 284 // Type 285 addByte((byte) 1); 286 addByte((byte) 0); 287 addByte((byte) 0); 288 addByte((byte) 0); 289 290 // Flags 291 addByte((byte) 6); 292 addByte((byte) 82); 293 addByte((byte) 0); 294 addByte((byte) 0); 295 296 // Domain length (first time). 297 int iDomLen = domainBytes.length; 298 byte[] domLen = convertShort(iDomLen); 299 addByte(domLen[0]); 300 addByte(domLen[1]); 301 302 // Domain length (second time). 303 addByte(domLen[0]); 304 addByte(domLen[1]); 305 306 // Domain offset. 307 byte[] domOff = convertShort(hostBytes.length + 32); 308 addByte(domOff[0]); 309 addByte(domOff[1]); 310 addByte((byte) 0); 311 addByte((byte) 0); 312 313 // Host length (first time). 314 byte[] hostLen = convertShort(hostBytes.length); 315 addByte(hostLen[0]); 316 addByte(hostLen[1]); 317 318 // Host length (second time). 319 addByte(hostLen[0]); 320 addByte(hostLen[1]); 321 322 // Host offset (always 32). 323 byte[] hostOff = convertShort(32); 324 addByte(hostOff[0]); 325 addByte(hostOff[1]); 326 addByte((byte) 0); 327 addByte((byte) 0); 328 329 // Host String. 330 addBytes(hostBytes); 331 332 // Domain String. 333 addBytes(domainBytes); 334 335 return getResponse(); 336 } 337 338 /*** 339 * Extracts the server nonce out of the given message type 2. 340 * 341 * @param message the String containing the base64 encoded message. 342 * @return an array of 8 bytes that the server sent to be used when 343 * hashing the password. 344 */ 345 private byte[] parseType2Message(String message) { 346 // Decode the message first. 347 byte[] msg = Base64.decode(getBytes(message)); 348 byte[] nonce = new byte[8]; 349 // The nonce is the 8 bytes starting from the byte in position 24. 350 for (int i = 0; i < 8; i++) { 351 nonce[i] = msg[i + 24]; 352 } 353 return nonce; 354 } 355 356 /*** 357 * Creates the type 3 message using the given server nonce. The type 3 message includes all the 358 * information for authentication, host, domain, username and the result of encrypting the 359 * nonce sent by the server using the user's password as the key. 360 * 361 * @param user The user name. This should not include the domain name. 362 * @param password The password. 363 * @param host The host that is originating the authentication request. 364 * @param domain The domain to authenticate within. 365 * @param nonce the 8 byte array the server sent. 366 * @return The type 3 message. 367 * @throws HttpException If {@encrypt(byte[],byte[])} fails. 368 */ 369 private String getType3Message(String user, String password, 370 String host, String domain, byte[] nonce) 371 throws HttpException { 372 373 int ntRespLen = 0; 374 int lmRespLen = 24; 375 domain = domain.toUpperCase(); 376 host = host.toUpperCase(); 377 user = user.toUpperCase(); 378 byte[] domainBytes = getBytes(domain); 379 byte[] hostBytes = getBytes(host); 380 byte[] userBytes = getBytes(user); 381 int domainLen = domainBytes.length; 382 int hostLen = hostBytes.length; 383 int userLen = userBytes.length; 384 int finalLength = 64 + ntRespLen + lmRespLen + domainLen 385 + userLen + hostLen; 386 prepareResponse(finalLength); 387 byte[] ntlmssp = getBytes("NTLMSSP"); 388 addBytes(ntlmssp); 389 addByte((byte) 0); 390 addByte((byte) 3); 391 addByte((byte) 0); 392 addByte((byte) 0); 393 addByte((byte) 0); 394 395 // LM Resp Length (twice) 396 addBytes(convertShort(24)); 397 addBytes(convertShort(24)); 398 399 // LM Resp Offset 400 addBytes(convertShort(finalLength - 24)); 401 addByte((byte) 0); 402 addByte((byte) 0); 403 404 // NT Resp Length (twice) 405 addBytes(convertShort(0)); 406 addBytes(convertShort(0)); 407 408 // NT Resp Offset 409 addBytes(convertShort(finalLength)); 410 addByte((byte) 0); 411 addByte((byte) 0); 412 413 // Domain length (twice) 414 addBytes(convertShort(domainLen)); 415 addBytes(convertShort(domainLen)); 416 417 // Domain offset. 418 addBytes(convertShort(64)); 419 addByte((byte) 0); 420 addByte((byte) 0); 421 422 // User Length (twice) 423 addBytes(convertShort(userLen)); 424 addBytes(convertShort(userLen)); 425 426 // User offset 427 addBytes(convertShort(64 + domainLen)); 428 addByte((byte) 0); 429 addByte((byte) 0); 430 431 // Host length (twice) 432 addBytes(convertShort(hostLen)); 433 addBytes(convertShort(hostLen)); 434 435 // Host offset 436 addBytes(convertShort(64 + domainLen + userLen)); 437 438 for (int i = 0; i < 6; i++) { 439 addByte((byte) 0); 440 } 441 442 // Message length 443 addBytes(convertShort(finalLength)); 444 addByte((byte) 0); 445 addByte((byte) 0); 446 447 // Flags 448 addByte((byte) 6); 449 addByte((byte) 82); 450 addByte((byte) 0); 451 addByte((byte) 0); 452 453 addBytes(domainBytes); 454 addBytes(userBytes); 455 addBytes(hostBytes); 456 addBytes(hashPassword(password, nonce)); 457 return getResponse(); 458 } 459 460 /*** 461 * Creates the LANManager and NT response for the given password using the 462 * given nonce. 463 * @param password the password to create a hash for. 464 * @param nonce the nonce sent by the server. 465 * @return The response. 466 * @throws HttpException If {@link #encrypt(byte[],byte[])} fails. 467 */ 468 private byte[] hashPassword(String password, byte[] nonce) 469 throws HttpException { 470 byte[] passw = getBytes(password.toUpperCase()); 471 byte[] lmPw1 = new byte[7]; 472 byte[] lmPw2 = new byte[7]; 473 474 int len = passw.length; 475 if (len > 7) { 476 len = 7; 477 } 478 479 int idx; 480 for (idx = 0; idx < len; idx++) { 481 lmPw1[idx] = passw[idx]; 482 } 483 for (; idx < 7; idx++) { 484 lmPw1[idx] = (byte) 0; 485 } 486 487 len = passw.length; 488 if (len > 14) { 489 len = 14; 490 } 491 for (idx = 7; idx < len; idx++) { 492 lmPw2[idx - 7] = passw[idx]; 493 } 494 for (; idx < 14; idx++) { 495 lmPw2[idx - 7] = (byte) 0; 496 } 497 498 // Create LanManager hashed Password 499 byte[] magic = { 500 (byte) 0x4B, (byte) 0x47, (byte) 0x53, (byte) 0x21, 501 (byte) 0x40, (byte) 0x23, (byte) 0x24, (byte) 0x25 502 }; 503 504 byte[] lmHpw1; 505 lmHpw1 = encrypt(lmPw1, magic); 506 507 byte[] lmHpw2 = encrypt(lmPw2, magic); 508 509 byte[] lmHpw = new byte[21]; 510 for (int i = 0; i < lmHpw1.length; i++) { 511 lmHpw[i] = lmHpw1[i]; 512 } 513 for (int i = 0; i < lmHpw2.length; i++) { 514 lmHpw[i + 8] = lmHpw2[i]; 515 } 516 for (int i = 0; i < 5; i++) { 517 lmHpw[i + 16] = (byte) 0; 518 } 519 520 // Create the responses. 521 byte[] lmResp = new byte[24]; 522 calcResp(lmHpw, nonce, lmResp); 523 524 return lmResp; 525 } 526 527 /*** 528 * Takes a 21 byte array and treats it as 3 56-bit DES keys. The 8 byte 529 * plaintext is encrypted with each key and the resulting 24 bytes are 530 * stored in the results array. 531 * 532 * @param keys The keys. 533 * @param plaintext The plain text to encrypt. 534 * @param results Where the results are stored. 535 * @throws HttpException If {@link #encrypt(byte[],byte[])} fails. 536 */ 537 private void calcResp(byte[] keys, byte[] plaintext, byte[] results) 538 throws HttpException { 539 byte[] keys1 = new byte[7]; 540 byte[] keys2 = new byte[7]; 541 byte[] keys3 = new byte[7]; 542 for (int i = 0; i < 7; i++) { 543 keys1[i] = keys[i]; 544 } 545 546 for (int i = 0; i < 7; i++) { 547 keys2[i] = keys[i + 7]; 548 } 549 550 for (int i = 0; i < 7; i++) { 551 keys3[i] = keys[i + 14]; 552 } 553 byte[] results1 = encrypt(keys1, plaintext); 554 555 byte[] results2 = encrypt(keys2, plaintext); 556 557 byte[] results3 = encrypt(keys3, plaintext); 558 559 for (int i = 0; i < 8; i++) { 560 results[i] = results1[i]; 561 } 562 for (int i = 0; i < 8; i++) { 563 results[i + 8] = results2[i]; 564 } 565 for (int i = 0; i < 8; i++) { 566 results[i + 16] = results3[i]; 567 } 568 } 569 570 /*** 571 * Converts a given number to a two byte array in little endian order. 572 * @param num the number to convert. 573 * @return The byte representation of <i>num</i> in little endian order. 574 */ 575 private byte[] convertShort(int num) { 576 byte[] val = new byte[2]; 577 String hex = Integer.toString(num, 16); 578 while (hex.length() < 4) { 579 hex = "0" + hex; 580 } 581 String low = hex.substring(2, 4); 582 String high = hex.substring(0, 2); 583 584 val[0] = (byte) Integer.parseInt(low, 16); 585 val[1] = (byte) Integer.parseInt(high, 16); 586 return val; 587 } 588 589 /*** 590 * Convert a string to a byte array. 591 * @param s The string 592 * @return byte[] The resulting byte array. 593 */ 594 private static byte[] getBytes(final String s) { 595 if (s == null) { 596 throw new IllegalArgumentException("Parameter may not be null"); 597 } 598 try { 599 return s.getBytes(DEFAULT_CHARSET); 600 } catch (UnsupportedEncodingException unexpectedEncodingException) { 601 throw new RuntimeException("NTLM requires ASCII support"); 602 } 603 } 604 }

This page was automatically generated by Maven