View Javadoc
1 /* 2 * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/NTLM.java,v 1.12 2003/02/11 03:41:14 jsdever Exp $ 3 * $Revision: 1.12 $ 4 * $Date: 2003/02/11 03:41:14 $ 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 import java.security.Security; 70 71 import javax.crypto.BadPaddingException; 72 import javax.crypto.Cipher; 73 import javax.crypto.IllegalBlockSizeException; 74 import javax.crypto.NoSuchPaddingException; 75 import javax.crypto.spec.SecretKeySpec; 76 77 import org.apache.commons.httpclient.util.Base64; 78 import org.apache.commons.logging.Log; 79 import org.apache.commons.logging.LogFactory; 80 81 /*** 82 * Provides an implementation of the NTLM authentication protocol. 83 * <p> 84 * This class provides methods for generating authentication 85 * challenge responses for the NTLM authentication protocol. The NTLM 86 * protocol is a proprietary Microsoft protocol and as such no RFC 87 * exists for it. This class is based upon the reverse engineering 88 * efforts of a wide range of people.</p> 89 * 90 * @deprecated this class will be made package access for 2.0beta2 91 * 92 * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a> 93 * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a> 94 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> 95 * 96 * @version $Revision: 1.12 $ $Date: 2003/02/11 03:41:14 $ 97 * @since 2.0alpha2 98 */ 99 public final class NTLM { 100 101 /*** The current response */ 102 private byte[] currentResponse; 103 104 /*** The current position */ 105 private int currentPosition = 0; 106 107 /*** Log object for this class. */ 108 private static final Log LOG = LogFactory.getLog(NTLM.class); 109 110 /*** Character encoding */ 111 public static final String DEFAULT_CHARSET = "ASCII"; 112 113 //Initialize the security provider 114 static { 115 //TODO: do not use System properties 116 final String secProviderName 117 = System.getProperty("httpclient.security.provider", 118 "com.sun.crypto.provider.SunJCE"); 119 try { 120 java.security.Provider secProvider = (java.security.Provider) 121 Class.forName(secProviderName).newInstance(); 122 Security.addProvider(secProvider); 123 } catch (ClassNotFoundException e) { 124 LOG.error("Specified security provider " + secProviderName 125 + " could not be found by the class loader", e); 126 } catch (ClassCastException e) { 127 LOG.error("Specified security provider " + secProviderName 128 + " is not of type java.security.Provider", e); 129 } catch (InstantiationException e) { 130 LOG.error("Specified security provider " + secProviderName 131 + " could not be instantiated", e); 132 } catch (IllegalAccessException e) { 133 LOG.error("Specified security provider " + secProviderName 134 + " does not allow access to the constructor", e); 135 } 136 } 137 138 /*** 139 * Returns the response for the given message. 140 * 141 * @param message the message that was received from the server. 142 * @param username the username to authenticate with. 143 * @param password the password to authenticate with. 144 * @param host The host. 145 * @param domain the NT domain to authenticate in. 146 * @return The response. 147 * @throws HttpException If the messages cannot be retrieved. 148 */ 149 public final String getResponseFor(String message, 150 String username, String password, String host, String domain) 151 throws HttpException { 152 153 final String response; 154 if (message == null || message.trim().equals("")) { 155 response = getType1Message(host, domain); 156 } else { 157 response = getType3Message(username, password, host, domain, 158 parseType2Message(message)); 159 } 160 return response; 161 } 162 163 /*** 164 * Return the cipher for the specified key. 165 * @param key The key. 166 * @return Cipher The cipher. 167 * @throws HttpException If the cipher cannot be retrieved. 168 */ 169 private Cipher getCipher(byte[] key) throws HttpException { 170 try { 171 final Cipher ecipher = Cipher.getInstance("DES/ECB/NoPadding"); 172 key = setupKey(key); 173 ecipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "DES")); 174 return ecipher; 175 } catch (NoSuchAlgorithmException e) { 176 throw new HttpException("DES encryption is not available."); 177 } catch (InvalidKeyException e) { 178 throw new HttpException("Invalid key for DES encryption."); 179 } catch (NoSuchPaddingException e) { 180 throw new HttpException( 181 "NoPadding option for DES is not available."); 182 } 183 } 184 185 /*** 186 * Adds parity bits to the key. 187 * @param key56 The key 188 * @return The modified key. 189 */ 190 private byte[] setupKey(byte[] key56) { 191 byte[] key = new byte[8]; 192 key[0] = (byte) ((key56[0] >> 1) & 0xff); 193 key[1] = (byte) ((((key56[0] & 0x01) << 6) 194 | (((key56[1] & 0xff) >> 2) & 0xff)) & 0xff); 195 key[2] = (byte) ((((key56[1] & 0x03) << 5) 196 | (((key56[2] & 0xff) >> 3) & 0xff)) & 0xff); 197 key[3] = (byte) ((((key56[2] & 0x07) << 4) 198 | (((key56[3] & 0xff) >> 4) & 0xff)) & 0xff); 199 key[4] = (byte) ((((key56[3] & 0x0f) << 3) 200 | (((key56[4] & 0xff) >> 5) & 0xff)) & 0xff); 201 key[5] = (byte) ((((key56[4] & 0x1f) << 2) 202 | (((key56[5] & 0xff) >> 6) & 0xff)) & 0xff); 203 key[6] = (byte) ((((key56[5] & 0x3f) << 1) 204 | (((key56[6] & 0xff) >> 7) & 0xff)) & 0xff); 205 key[7] = (byte) (key56[6] & 0x7f); 206 207 for (int i = 0; i < key.length; i++) { 208 key[i] = (byte) (key[i] << 1); 209 } 210 return key; 211 } 212 213 /*** 214 * Encrypt the data. 215 * @param key The key. 216 * @param bytes The data 217 * @return byte[] The encrypted data 218 * @throws HttpException If {@link Cipher.doFinal(byte[])} fails 219 */ 220 private byte[] encrypt(byte[] key, byte[] bytes) 221 throws HttpException { 222 Cipher ecipher = getCipher(key); 223 try { 224 byte[] enc = ecipher.doFinal(bytes); 225 return enc; 226 } catch (IllegalBlockSizeException e) { 227 throw new HttpException("Invalid block size for DES encryption."); 228 } catch (BadPaddingException e) { 229 throw new HttpException( 230 "Data not padded correctly for DES encryption."); 231 } 232 } 233 234 /*** 235 * Prepares the object to create a response of the given length. 236 * @param length the length of the response to prepare. 237 */ 238 private void prepareResponse(int length) { 239 currentResponse = new byte[length]; 240 currentPosition = 0; 241 } 242 243 /*** 244 * Adds the given byte to the response. 245 * @param b the byte to add. 246 */ 247 private void addByte(byte b) { 248 currentResponse[currentPosition] = b; 249 currentPosition++; 250 } 251 252 /*** 253 * Adds the given bytes to the response. 254 * @param bytes the bytes to add. 255 */ 256 private void addBytes(byte[] bytes) { 257 for (int i = 0; i < bytes.length; i++) { 258 currentResponse[currentPosition] = bytes[i]; 259 currentPosition++; 260 } 261 } 262 263 /*** 264 * Returns the response that has been generated after shrinking the array if 265 * required and base64 encodes the response. 266 * @return The response as above. 267 */ 268 private String getResponse() { 269 byte[] resp; 270 if (currentResponse.length > currentPosition) { 271 byte[] tmp = new byte[currentPosition]; 272 for (int i = 0; i < currentPosition; i++) { 273 tmp[i] = currentResponse[i]; 274 } 275 resp = tmp; 276 } else { 277 resp = currentResponse; 278 } 279 return HttpConstants.getString(Base64.encode(resp)); 280 } 281 282 /*** 283 * TODO: Figure out what this method really does. 284 * @param host The host 285 * @param domain The domain 286 * @return String 287 */ 288 private String getType1Message(String host, String domain) { 289 host = host.toUpperCase(); 290 domain = domain.toUpperCase(); 291 byte[] hostBytes = getBytes(host); 292 byte[] domainBytes = getBytes(domain); 293 294 int finalLength = 32 + hostBytes.length + domainBytes.length; 295 prepareResponse(finalLength); 296 297 // The initial id string. 298 byte[] protocol = getBytes("NTLMSSP"); 299 addBytes(protocol); 300 addByte((byte) 0); 301 302 // Type 303 addByte((byte) 1); 304 addByte((byte) 0); 305 addByte((byte) 0); 306 addByte((byte) 0); 307 308 // Flags 309 addByte((byte) 6); 310 addByte((byte) 82); 311 addByte((byte) 0); 312 addByte((byte) 0); 313 314 // Domain length (first time). 315 int iDomLen = domainBytes.length; 316 byte[] domLen = convertShort(iDomLen); 317 addByte(domLen[0]); 318 addByte(domLen[1]); 319 320 // Domain length (second time). 321 addByte(domLen[0]); 322 addByte(domLen[1]); 323 324 // Domain offset. 325 byte[] domOff = convertShort(hostBytes.length + 32); 326 addByte(domOff[0]); 327 addByte(domOff[1]); 328 addByte((byte) 0); 329 addByte((byte) 0); 330 331 // Host length (first time). 332 byte[] hostLen = convertShort(hostBytes.length); 333 addByte(hostLen[0]); 334 addByte(hostLen[1]); 335 336 // Host length (second time). 337 addByte(hostLen[0]); 338 addByte(hostLen[1]); 339 340 // Host offset (always 32). 341 byte[] hostOff = convertShort(32); 342 addByte(hostOff[0]); 343 addByte(hostOff[1]); 344 addByte((byte) 0); 345 addByte((byte) 0); 346 347 // Host String. 348 addBytes(hostBytes); 349 350 // Domain String. 351 addBytes(domainBytes); 352 353 return getResponse(); 354 } 355 356 /*** 357 * Extracts the server nonce out of the given message type 2. 358 * 359 * @param message the String containing the base64 encoded message. 360 * @return an array of 8 bytes that the server sent to be used when 361 * hashing the password. 362 */ 363 private byte[] parseType2Message(String message) { 364 // Decode the message first. 365 byte[] msg = Base64.decode(getBytes(message)); 366 byte[] nonce = new byte[8]; 367 // The nonce is the 8 bytes starting from the byte in position 24. 368 for (int i = 0; i < 8; i++) { 369 nonce[i] = msg[i + 24]; 370 } 371 return nonce; 372 } 373 374 /*** 375 * Creates the type 3 message using the given server nonce. 376 * @param user The user. 377 * @param password The password. 378 * @param host The host. 379 * @param domain The domain. 380 * @param nonce the 8 byte array the server sent. 381 * @return The type 3 message. 382 * @throws HttpException If {@encrypt(byte[],byte[])} fails. 383 */ 384 private String getType3Message(String user, String password, 385 String host, String domain, byte[] nonce) 386 throws HttpException { 387 388 int ntRespLen = 0; 389 int lmRespLen = 24; 390 domain = domain.toUpperCase(); 391 host = host.toUpperCase(); 392 user = user.toUpperCase(); 393 byte[] domainBytes = getBytes(domain); 394 byte[] hostBytes = getBytes(host); 395 byte[] userBytes = getBytes(user); 396 int domainLen = domainBytes.length; 397 int hostLen = hostBytes.length; 398 int userLen = userBytes.length; 399 int finalLength = 64 + ntRespLen + lmRespLen + domainLen 400 + userLen + hostLen; 401 prepareResponse(finalLength); 402 byte[] ntlmssp = getBytes("NTLMSSP"); 403 addBytes(ntlmssp); 404 addByte((byte) 0); 405 addByte((byte) 3); 406 addByte((byte) 0); 407 addByte((byte) 0); 408 addByte((byte) 0); 409 410 // LM Resp Length (twice) 411 addBytes(convertShort(24)); 412 addBytes(convertShort(24)); 413 414 // LM Resp Offset 415 addBytes(convertShort(finalLength - 24)); 416 addByte((byte) 0); 417 addByte((byte) 0); 418 419 // NT Resp Length (twice) 420 addBytes(convertShort(0)); 421 addBytes(convertShort(0)); 422 423 // NT Resp Offset 424 addBytes(convertShort(finalLength)); 425 addByte((byte) 0); 426 addByte((byte) 0); 427 428 // Domain length (twice) 429 addBytes(convertShort(domainLen)); 430 addBytes(convertShort(domainLen)); 431 432 // Domain offset. 433 addBytes(convertShort(64)); 434 addByte((byte) 0); 435 addByte((byte) 0); 436 437 // User Length (twice) 438 addBytes(convertShort(userLen)); 439 addBytes(convertShort(userLen)); 440 441 // User offset 442 addBytes(convertShort(64 + domainLen)); 443 addByte((byte) 0); 444 addByte((byte) 0); 445 446 // Host length (twice) 447 addBytes(convertShort(hostLen)); 448 addBytes(convertShort(hostLen)); 449 450 // Host offset 451 addBytes(convertShort(64 + domainLen + userLen)); 452 453 for (int i = 0; i < 6; i++) { 454 addByte((byte) 0); 455 } 456 457 // Message length 458 addBytes(convertShort(finalLength)); 459 addByte((byte) 0); 460 addByte((byte) 0); 461 462 // Flags 463 addByte((byte) 6); 464 addByte((byte) 82); 465 addByte((byte) 0); 466 addByte((byte) 0); 467 468 addBytes(domainBytes); 469 addBytes(userBytes); 470 addBytes(hostBytes); 471 addBytes(hashPassword(password, nonce)); 472 return getResponse(); 473 } 474 475 /*** 476 * Creates the LANManager and NT response for the given password using the 477 * given nonce. 478 * @param password the password to create a hash for. 479 * @param nonce the nonce sent by the server. 480 * @return The response. 481 * @throws HttpException If {@link #encrypt(byte[],byte[])} fails. 482 */ 483 private byte[] hashPassword(String password, byte[] nonce) 484 throws HttpException { 485 byte[] passw = getBytes(password.toUpperCase()); 486 byte[] lmPw1 = new byte[7]; 487 byte[] lmPw2 = new byte[7]; 488 489 int len = passw.length; 490 if (len > 7) { 491 len = 7; 492 } 493 494 int idx; 495 for (idx = 0; idx < len; idx++) { 496 lmPw1[idx] = passw[idx]; 497 } 498 for (; idx < 7; idx++) { 499 lmPw1[idx] = (byte) 0; 500 } 501 502 len = passw.length; 503 if (len > 14) { 504 len = 14; 505 } 506 for (idx = 7; idx < len; idx++) { 507 lmPw2[idx - 7] = passw[idx]; 508 } 509 for (; idx < 14; idx++) { 510 lmPw2[idx - 7] = (byte) 0; 511 } 512 513 // Create LanManager hashed Password 514 byte[] magic = { 515 (byte) 0x4B, (byte) 0x47, (byte) 0x53, (byte) 0x21, 516 (byte) 0x40, (byte) 0x23, (byte) 0x24, (byte) 0x25 517 }; 518 519 byte[] lmHpw1; 520 lmHpw1 = encrypt(lmPw1, magic); 521 522 byte[] lmHpw2 = encrypt(lmPw2, magic); 523 524 byte[] lmHpw = new byte[21]; 525 for (int i = 0; i < lmHpw1.length; i++) { 526 lmHpw[i] = lmHpw1[i]; 527 } 528 for (int i = 0; i < lmHpw2.length; i++) { 529 lmHpw[i + 8] = lmHpw2[i]; 530 } 531 for (int i = 0; i < 5; i++) { 532 lmHpw[i + 16] = (byte) 0; 533 } 534 535 // Create the responses. 536 byte[] lmResp = new byte[24]; 537 calcResp(lmHpw, nonce, lmResp); 538 539 return lmResp; 540 } 541 542 /*** 543 * Takes a 21 byte array and treats it as 3 56-bit DES keys. The 8 byte 544 * plaintext is encrypted with each key and the resulting 24 bytes are 545 * stored in the results array. 546 * 547 * @param keys The keys. 548 * @param plaintext The plain text to encrypt. 549 * @param results Where the results are stored. 550 * @throws HttpException If {@link #encrypt(byte[],byte[])} fails. 551 */ 552 private void calcResp(byte[] keys, byte[] plaintext, byte[] results) 553 throws HttpException { 554 byte[] keys1 = new byte[7]; 555 byte[] keys2 = new byte[7]; 556 byte[] keys3 = new byte[7]; 557 for (int i = 0; i < 7; i++) { 558 keys1[i] = keys[i]; 559 } 560 561 for (int i = 0; i < 7; i++) { 562 keys2[i] = keys[i + 7]; 563 } 564 565 for (int i = 0; i < 7; i++) { 566 keys3[i] = keys[i + 14]; 567 } 568 byte[] results1 = encrypt(keys1, plaintext); 569 570 byte[] results2 = encrypt(keys2, plaintext); 571 572 byte[] results3 = encrypt(keys3, plaintext); 573 574 for (int i = 0; i < 8; i++) { 575 results[i] = results1[i]; 576 } 577 for (int i = 0; i < 8; i++) { 578 results[i + 8] = results2[i]; 579 } 580 for (int i = 0; i < 8; i++) { 581 results[i + 16] = results3[i]; 582 } 583 } 584 585 /*** 586 * Converts a given number to a two byte array in little endian order. 587 * @param num the number to convert. 588 * @return The new array. 589 */ 590 private byte[] convertShort(int num) { 591 byte[] val = new byte[2]; 592 String hex = Integer.toString(num, 16); 593 while (hex.length() < 4) { 594 hex = "0" + hex; 595 } 596 String low = hex.substring(2, 4); 597 String high = hex.substring(0, 2); 598 599 val[0] = (byte) Integer.parseInt(low, 16); 600 val[1] = (byte) Integer.parseInt(high, 16); 601 return val; 602 } 603 604 /*** 605 * Convert a string to a byte array. 606 * @param s The string 607 * @return byte[] The resulting byte array. 608 */ 609 private static byte[] getBytes(final String s) { 610 if (s == null) { 611 throw new IllegalArgumentException("Parameter may not be null"); 612 } 613 try { 614 return s.getBytes(DEFAULT_CHARSET); 615 } catch (UnsupportedEncodingException unexpectedEncodingException) { 616 throw new RuntimeException("NTLM requires ASCII support"); 617 } 618 } 619 }

This page was automatically generated by Maven