- /*
- * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/NTLM.java,v 1.11 2004/05/13 04:02:00 mbecke Exp $
- * $Revision: 1.11 $
- * $Date: 2004/05/13 04:02:00 $
- *
- * ====================================================================
- *
- * Copyright 2002-2004 The Apache Software Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
- package org.apache.commons.httpclient.auth;
-
- import java.security.InvalidKeyException;
- import java.security.NoSuchAlgorithmException;
-
- import javax.crypto.BadPaddingException;
- import javax.crypto.Cipher;
- import javax.crypto.IllegalBlockSizeException;
- import javax.crypto.NoSuchPaddingException;
- import javax.crypto.spec.SecretKeySpec;
-
- import org.apache.commons.codec.binary.Base64;
- import org.apache.commons.httpclient.util.EncodingUtil;
-
- /**
- * Provides an implementation of the NTLM authentication protocol.
- * <p>
- * This class provides methods for generating authentication
- * challenge responses for the NTLM authentication protocol. The NTLM
- * protocol is a proprietary Microsoft protocol and as such no RFC
- * exists for it. This class is based upon the reverse engineering
- * efforts of a wide range of people.</p>
- *
- * <p>Please note that an implementation of JCE must be correctly installed and configured when
- * using NTLM support.</p>
- *
- * <p>This class should not be used externally to HttpClient as it's API is specifically
- * designed to work with HttpClient's use case, in particular it's connection management.</p>
- *
- * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a>
- * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
- * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
- *
- * @version $Revision: 1.11 $ $Date: 2004/05/13 04:02:00 $
- * @since 3.0
- */
- final class NTLM {
-
- /** Character encoding */
- public static final String DEFAULT_CHARSET = "ASCII";
-
- /** The current response */
- private byte[] currentResponse;
-
- /** The current position */
- private int currentPosition = 0;
-
- /** The character set to use for encoding the credentials */
- private String credentialCharset = DEFAULT_CHARSET;
-
- /**
- * Returns the response for the given message.
- *
- * @param message the message that was received from the server.
- * @param username the username to authenticate with.
- * @param password the password to authenticate with.
- * @param host The host.
- * @param domain the NT domain to authenticate in.
- * @return The response.
- * @throws HttpException If the messages cannot be retrieved.
- */
- public final String getResponseFor(String message,
- String username, String password, String host, String domain)
- throws AuthenticationException {
-
- final String response;
- if (message == null || message.trim().equals("")) {
- response = getType1Message(host, domain);
- } else {
- response = getType3Message(username, password, host, domain,
- parseType2Message(message));
- }
- return response;
- }
-
- /**
- * Return the cipher for the specified key.
- * @param key The key.
- * @return Cipher The cipher.
- * @throws AuthenticationException If the cipher cannot be retrieved.
- */
- private Cipher getCipher(byte[] key) throws AuthenticationException {
- try {
- final Cipher ecipher = Cipher.getInstance("DES/ECB/NoPadding");
- key = setupKey(key);
- ecipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "DES"));
- return ecipher;
- } catch (NoSuchAlgorithmException e) {
- throw new AuthenticationException("DES encryption is not available.", e);
- } catch (InvalidKeyException e) {
- throw new AuthenticationException("Invalid key for DES encryption.", e);
- } catch (NoSuchPaddingException e) {
- throw new AuthenticationException(
- "NoPadding option for DES is not available.", e);
- }
- }
-
- /**
- * Adds parity bits to the key.
- * @param key56 The key
- * @return The modified key.
- */
- private byte[] setupKey(byte[] key56) {
- byte[] key = new byte[8];
- key[0] = (byte) ((key56[0] >> 1) & 0xff);
- key[1] = (byte) ((((key56[0] & 0x01) << 6)
- | (((key56[1] & 0xff) >> 2) & 0xff)) & 0xff);
- key[2] = (byte) ((((key56[1] & 0x03) << 5)
- | (((key56[2] & 0xff) >> 3) & 0xff)) & 0xff);
- key[3] = (byte) ((((key56[2] & 0x07) << 4)
- | (((key56[3] & 0xff) >> 4) & 0xff)) & 0xff);
- key[4] = (byte) ((((key56[3] & 0x0f) << 3)
- | (((key56[4] & 0xff) >> 5) & 0xff)) & 0xff);
- key[5] = (byte) ((((key56[4] & 0x1f) << 2)
- | (((key56[5] & 0xff) >> 6) & 0xff)) & 0xff);
- key[6] = (byte) ((((key56[5] & 0x3f) << 1)
- | (((key56[6] & 0xff) >> 7) & 0xff)) & 0xff);
- key[7] = (byte) (key56[6] & 0x7f);
-
- for (int i = 0; i < key.length; i++) {
- key[i] = (byte) (key[i] << 1);
- }
- return key;
- }
-
- /**
- * Encrypt the data.
- * @param key The key.
- * @param bytes The data
- * @return byte[] The encrypted data
- * @throws HttpException If {@link Cipher.doFinal(byte[])} fails
- */
- private byte[] encrypt(byte[] key, byte[] bytes)
- throws AuthenticationException {
- Cipher ecipher = getCipher(key);
- try {
- byte[] enc = ecipher.doFinal(bytes);
- return enc;
- } catch (IllegalBlockSizeException e) {
- throw new AuthenticationException("Invalid block size for DES encryption.", e);
- } catch (BadPaddingException e) {
- throw new AuthenticationException("Data not padded correctly for DES encryption.", e);
- }
- }
-
- /**
- * Prepares the object to create a response of the given length.
- * @param length the length of the response to prepare.
- */
- private void prepareResponse(int length) {
- currentResponse = new byte[length];
- currentPosition = 0;
- }
-
- /**
- * Adds the given byte to the response.
- * @param b the byte to add.
- */
- private void addByte(byte b) {
- currentResponse[currentPosition] = b;
- currentPosition++;
- }
-
- /**
- * Adds the given bytes to the response.
- * @param bytes the bytes to add.
- */
- private void addBytes(byte[] bytes) {
- for (int i = 0; i < bytes.length; i++) {
- currentResponse[currentPosition] = bytes[i];
- currentPosition++;
- }
- }
-
- /**
- * Returns the response that has been generated after shrinking the array if
- * required and base64 encodes the response.
- * @return The response as above.
- */
- private String getResponse() {
- byte[] resp;
- if (currentResponse.length > currentPosition) {
- byte[] tmp = new byte[currentPosition];
- for (int i = 0; i < currentPosition; i++) {
- tmp[i] = currentResponse[i];
- }
- resp = tmp;
- } else {
- resp = currentResponse;
- }
- return EncodingUtil.getAsciiString(Base64.encodeBase64(resp));
- }
-
- /**
- * Creates the first message (type 1 message) in the NTLM authentication sequence.
- * This message includes the user name, domain and host for the authentication session.
- *
- * @param host the computer name of the host requesting authentication.
- * @param domain The domain to authenticate with.
- * @return String the message to add to the HTTP request header.
- */
- public String getType1Message(String host, String domain) {
- host = host.toUpperCase();
- domain = domain.toUpperCase();
- byte[] hostBytes = EncodingUtil.getBytes(host, DEFAULT_CHARSET);
- byte[] domainBytes = EncodingUtil.getBytes(domain, DEFAULT_CHARSET);
-
- int finalLength = 32 + hostBytes.length + domainBytes.length;
- prepareResponse(finalLength);
-
- // The initial id string.
- byte[] protocol = EncodingUtil.getBytes("NTLMSSP", DEFAULT_CHARSET);
- addBytes(protocol);
- addByte((byte) 0);
-
- // Type
- addByte((byte) 1);
- addByte((byte) 0);
- addByte((byte) 0);
- addByte((byte) 0);
-
- // Flags
- addByte((byte) 6);
- addByte((byte) 82);
- addByte((byte) 0);
- addByte((byte) 0);
-
- // Domain length (first time).
- int iDomLen = domainBytes.length;
- byte[] domLen = convertShort(iDomLen);
- addByte(domLen[0]);
- addByte(domLen[1]);
-
- // Domain length (second time).
- addByte(domLen[0]);
- addByte(domLen[1]);
-
- // Domain offset.
- byte[] domOff = convertShort(hostBytes.length + 32);
- addByte(domOff[0]);
- addByte(domOff[1]);
- addByte((byte) 0);
- addByte((byte) 0);
-
- // Host length (first time).
- byte[] hostLen = convertShort(hostBytes.length);
- addByte(hostLen[0]);
- addByte(hostLen[1]);
-
- // Host length (second time).
- addByte(hostLen[0]);
- addByte(hostLen[1]);
-
- // Host offset (always 32).
- byte[] hostOff = convertShort(32);
- addByte(hostOff[0]);
- addByte(hostOff[1]);
- addByte((byte) 0);
- addByte((byte) 0);
-
- // Host String.
- addBytes(hostBytes);
-
- // Domain String.
- addBytes(domainBytes);
-
- return getResponse();
- }
-
- /**
- * Extracts the server nonce out of the given message type 2.
- *
- * @param message the String containing the base64 encoded message.
- * @return an array of 8 bytes that the server sent to be used when
- * hashing the password.
- */
- public byte[] parseType2Message(String message) {
- // Decode the message first.
- byte[] msg = Base64.decodeBase64(EncodingUtil.getBytes(message, DEFAULT_CHARSET));
- byte[] nonce = new byte[8];
- // The nonce is the 8 bytes starting from the byte in position 24.
- for (int i = 0; i < 8; i++) {
- nonce[i] = msg[i + 24];
- }
- return nonce;
- }
-
- /**
- * Creates the type 3 message using the given server nonce. The type 3 message includes all the
- * information for authentication, host, domain, username and the result of encrypting the
- * nonce sent by the server using the user's password as the key.
- *
- * @param user The user name. This should not include the domain name.
- * @param password The password.
- * @param host The host that is originating the authentication request.
- * @param domain The domain to authenticate within.
- * @param nonce the 8 byte array the server sent.
- * @return The type 3 message.
- * @throws AuthenticationException If {@encrypt(byte[],byte[])} fails.
- */
- public String getType3Message(String user, String password,
- String host, String domain, byte[] nonce)
- throws AuthenticationException {
-
- int ntRespLen = 0;
- int lmRespLen = 24;
- domain = domain.toUpperCase();
- host = host.toUpperCase();
- user = user.toUpperCase();
- byte[] domainBytes = EncodingUtil.getBytes(domain, DEFAULT_CHARSET);
- byte[] hostBytes = EncodingUtil.getBytes(host, DEFAULT_CHARSET);
- byte[] userBytes = EncodingUtil.getBytes(user, credentialCharset);
- int domainLen = domainBytes.length;
- int hostLen = hostBytes.length;
- int userLen = userBytes.length;
- int finalLength = 64 + ntRespLen + lmRespLen + domainLen
- + userLen + hostLen;
- prepareResponse(finalLength);
- byte[] ntlmssp = EncodingUtil.getBytes("NTLMSSP", DEFAULT_CHARSET);
- addBytes(ntlmssp);
- addByte((byte) 0);
- addByte((byte) 3);
- addByte((byte) 0);
- addByte((byte) 0);
- addByte((byte) 0);
-
- // LM Resp Length (twice)
- addBytes(convertShort(24));
- addBytes(convertShort(24));
-
- // LM Resp Offset
- addBytes(convertShort(finalLength - 24));
- addByte((byte) 0);
- addByte((byte) 0);
-
- // NT Resp Length (twice)
- addBytes(convertShort(0));
- addBytes(convertShort(0));
-
- // NT Resp Offset
- addBytes(convertShort(finalLength));
- addByte((byte) 0);
- addByte((byte) 0);
-
- // Domain length (twice)
- addBytes(convertShort(domainLen));
- addBytes(convertShort(domainLen));
-
- // Domain offset.
- addBytes(convertShort(64));
- addByte((byte) 0);
- addByte((byte) 0);
-
- // User Length (twice)
- addBytes(convertShort(userLen));
- addBytes(convertShort(userLen));
-
- // User offset
- addBytes(convertShort(64 + domainLen));
- addByte((byte) 0);
- addByte((byte) 0);
-
- // Host length (twice)
- addBytes(convertShort(hostLen));
- addBytes(convertShort(hostLen));
-
- // Host offset
- addBytes(convertShort(64 + domainLen + userLen));
-
- for (int i = 0; i < 6; i++) {
- addByte((byte) 0);
- }
-
- // Message length
- addBytes(convertShort(finalLength));
- addByte((byte) 0);
- addByte((byte) 0);
-
- // Flags
- addByte((byte) 6);
- addByte((byte) 82);
- addByte((byte) 0);
- addByte((byte) 0);
-
- addBytes(domainBytes);
- addBytes(userBytes);
- addBytes(hostBytes);
- addBytes(hashPassword(password, nonce));
- return getResponse();
- }
-
- /**
- * Creates the LANManager and NT response for the given password using the
- * given nonce.
- * @param password the password to create a hash for.
- * @param nonce the nonce sent by the server.
- * @return The response.
- * @throws HttpException If {@link #encrypt(byte[],byte[])} fails.
- */
- private byte[] hashPassword(String password, byte[] nonce)
- throws AuthenticationException {
- byte[] passw = EncodingUtil.getBytes(password.toUpperCase(), credentialCharset);
- byte[] lmPw1 = new byte[7];
- byte[] lmPw2 = new byte[7];
-
- int len = passw.length;
- if (len > 7) {
- len = 7;
- }
-
- int idx;
- for (idx = 0; idx < len; idx++) {
- lmPw1[idx] = passw[idx];
- }
- for (; idx < 7; idx++) {
- lmPw1[idx] = (byte) 0;
- }
-
- len = passw.length;
- if (len > 14) {
- len = 14;
- }
- for (idx = 7; idx < len; idx++) {
- lmPw2[idx - 7] = passw[idx];
- }
- for (; idx < 14; idx++) {
- lmPw2[idx - 7] = (byte) 0;
- }
-
- // Create LanManager hashed Password
- byte[] magic = {
- (byte) 0x4B, (byte) 0x47, (byte) 0x53, (byte) 0x21,
- (byte) 0x40, (byte) 0x23, (byte) 0x24, (byte) 0x25
- };
-
- byte[] lmHpw1;
- lmHpw1 = encrypt(lmPw1, magic);
-
- byte[] lmHpw2 = encrypt(lmPw2, magic);
-
- byte[] lmHpw = new byte[21];
- for (int i = 0; i < lmHpw1.length; i++) {
- lmHpw[i] = lmHpw1[i];
- }
- for (int i = 0; i < lmHpw2.length; i++) {
- lmHpw[i + 8] = lmHpw2[i];
- }
- for (int i = 0; i < 5; i++) {
- lmHpw[i + 16] = (byte) 0;
- }
-
- // Create the responses.
- byte[] lmResp = new byte[24];
- calcResp(lmHpw, nonce, lmResp);
-
- return lmResp;
- }
-
- /**
- * Takes a 21 byte array and treats it as 3 56-bit DES keys. The 8 byte
- * plaintext is encrypted with each key and the resulting 24 bytes are
- * stored in the results array.
- *
- * @param keys The keys.
- * @param plaintext The plain text to encrypt.
- * @param results Where the results are stored.
- * @throws AuthenticationException If {@link #encrypt(byte[],byte[])} fails.
- */
- private void calcResp(byte[] keys, byte[] plaintext, byte[] results)
- throws AuthenticationException {
- byte[] keys1 = new byte[7];
- byte[] keys2 = new byte[7];
- byte[] keys3 = new byte[7];
- for (int i = 0; i < 7; i++) {
- keys1[i] = keys[i];
- }
-
- for (int i = 0; i < 7; i++) {
- keys2[i] = keys[i + 7];
- }
-
- for (int i = 0; i < 7; i++) {
- keys3[i] = keys[i + 14];
- }
- byte[] results1 = encrypt(keys1, plaintext);
-
- byte[] results2 = encrypt(keys2, plaintext);
-
- byte[] results3 = encrypt(keys3, plaintext);
-
- for (int i = 0; i < 8; i++) {
- results[i] = results1[i];
- }
- for (int i = 0; i < 8; i++) {
- results[i + 8] = results2[i];
- }
- for (int i = 0; i < 8; i++) {
- results[i + 16] = results3[i];
- }
- }
-
- /**
- * Converts a given number to a two byte array in little endian order.
- * @param num the number to convert.
- * @return The byte representation of <i>num</i> in little endian order.
- */
- private byte[] convertShort(int num) {
- byte[] val = new byte[2];
- String hex = Integer.toString(num, 16);
- while (hex.length() < 4) {
- hex = "0" + hex;
- }
- String low = hex.substring(2, 4);
- String high = hex.substring(0, 2);
-
- val[0] = (byte) Integer.parseInt(low, 16);
- val[1] = (byte) Integer.parseInt(high, 16);
- return val;
- }
-
- /**
- * @return Returns the credentialCharset.
- */
- public String getCredentialCharset() {
- return credentialCharset;
- }
-
- /**
- * @param credentialCharset The credentialCharset to set.
- */
- public void setCredentialCharset(String credentialCharset) {
- this.credentialCharset = credentialCharset;
- }
-
- }