View Javadoc

    1   /*
    2    * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/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 $
    3    * $Revision: 480424 $
    4    * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
    5    *
    6    * ====================================================================
    7    *
    8    *  Licensed to the Apache Software Foundation (ASF) under one or more
    9    *  contributor license agreements.  See the NOTICE file distributed with
    10   *  this work for additional information regarding copyright ownership.
    11   *  The ASF licenses this file to You under the Apache License, Version 2.0
    12   *  (the "License"); you may not use this file except in compliance with
    13   *  the License.  You may obtain a copy of the License at
    14   *
    15   *      http://www.apache.org/licenses/LICENSE-2.0
    16   *
    17   *  Unless required by applicable law or agreed to in writing, software
    18   *  distributed under the License is distributed on an "AS IS" BASIS,
    19   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    20   *  See the License for the specific language governing permissions and
    21   *  limitations under the License.
    22   * ====================================================================
    23   *
    24   * This software consists of voluntary contributions made by many
    25   * individuals on behalf of the Apache Software Foundation.  For more
    26   * information on the Apache Software Foundation, please see
    27   * <http://www.apache.org/>.
    28   *
    29   */
    30
    31  package org.apache.commons.httpclient.auth;
    32
    33  import java.security.InvalidKeyException;
    34  import java.security.NoSuchAlgorithmException;
    35
    36  import javax.crypto.BadPaddingException;
    37  import javax.crypto.Cipher;
    38  import javax.crypto.IllegalBlockSizeException;
    39  import javax.crypto.NoSuchPaddingException;
    40  import javax.crypto.spec.SecretKeySpec;
    41
    42  import org.apache.commons.codec.binary.Base64;
    43  import org.apache.commons.httpclient.util.EncodingUtil;
    44
    45  /**
    46   * Provides an implementation of the NTLM authentication protocol.
    47   * <p>
    48   * This class provides methods for generating authentication
    49   * challenge responses for the NTLM authentication protocol.  The NTLM
    50   * protocol is a proprietary Microsoft protocol and as such no RFC
    51   * exists for it.  This class is based upon the reverse engineering
    52   * efforts of a wide range of people.</p>
    53   *
    54   * <p>Please note that an implementation of JCE must be correctly installed and configured when
    55   * using NTLM support.</p>
    56   *
    57   * <p>This class should not be used externally to HttpClient as it's API is specifically
    58   * designed to work with HttpClient's use case, in particular it's connection management.</p>
    59   *
    60   * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a>
    61   * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
    62   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
    63   *
    64   * @version $Revision: 480424 $ $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
    65   * @since 3.0
    66   */
    67  final class NTLM {
    68
    69      /** Character encoding */
    70      public static final String DEFAULT_CHARSET = "ASCII";
    71
    72      /** The current response */
    73      private byte[] currentResponse;
    74
    75      /** The current position */
    76      private int currentPosition = 0;
    77
    78      /** The character set to use for encoding the credentials */
    79      private String credentialCharset = DEFAULT_CHARSET;
    80
    81      /**
    82       * Returns the response for the given message.
    83       *
    84       * @param message the message that was received from the server.
    85       * @param username the username to authenticate with.
    86       * @param password the password to authenticate with.
    87       * @param host The host.
    88       * @param domain the NT domain to authenticate in.
    89       * @return The response.
    90       * @throws HttpException If the messages cannot be retrieved.
    91       */
    92      public final String getResponseFor(String message,
    93              String username, String password, String host, String domain)
    94              throws AuthenticationException {
    95
    96          final String response;
    97          if (message == null || message.trim().equals("")) {
    98              response = getType1Message(host, domain);
    99          } else {
    100             response = getType3Message(username, password, host, domain,
    101                     parseType2Message(message));
    102         }
    103         return response;
    104     }
    105
    106     /**
    107      * Return the cipher for the specified key.
    108      * @param key The key.
    109      * @return Cipher The cipher.
    110      * @throws AuthenticationException If the cipher cannot be retrieved.
    111      */
    112     private Cipher getCipher(byte[] key) throws AuthenticationException {
    113         try {
    114             final Cipher ecipher = Cipher.getInstance("DES/ECB/NoPadding");
    115             key = setupKey(key);
    116             ecipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "DES"));
    117             return ecipher;
    118         } catch (NoSuchAlgorithmException e) {
    119             throw new AuthenticationException("DES encryption is not available.", e);
    120         } catch (InvalidKeyException e) {
    121             throw new AuthenticationException("Invalid key for DES encryption.", e);
    122         } catch (NoSuchPaddingException e) {
    123             throw new AuthenticationException(
    124                 "NoPadding option for DES is not available.", e);
    125         }
    126     }
    127
    128     /**
    129      * Adds parity bits to the key.
    130      * @param key56 The key
    131      * @return The modified key.
    132      */
    133     private byte[] setupKey(byte[] key56) {
    134         byte[] key = new byte[8];
    135         key[0] = (byte) ((key56[0] >> 1) & 0xff);
    136         key[1] = (byte) ((((key56[0] & 0x01) << 6)
    137             | (((key56[1] & 0xff) >> 2) & 0xff)) & 0xff);
    138         key[2] = (byte) ((((key56[1] & 0x03) << 5)
    139             | (((key56[2] & 0xff) >> 3) & 0xff)) & 0xff);
    140         key[3] = (byte) ((((key56[2] & 0x07) << 4)
    141             | (((key56[3] & 0xff) >> 4) & 0xff)) & 0xff);
    142         key[4] = (byte) ((((key56[3] & 0x0f) << 3)
    143             | (((key56[4] & 0xff) >> 5) & 0xff)) & 0xff);
    144         key[5] = (byte) ((((key56[4] & 0x1f) << 2)
    145             | (((key56[5] & 0xff) >> 6) & 0xff)) & 0xff);
    146         key[6] = (byte) ((((key56[5] & 0x3f) << 1)
    147             | (((key56[6] & 0xff) >> 7) & 0xff)) & 0xff);
    148         key[7] = (byte) (key56[6] & 0x7f);
    149
    150         for (int i = 0; i < key.length; i++) {
    151             key[i] = (byte) (key[i] << 1);
    152         }
    153         return key;
    154     }
    155
    156     /**
    157      * Encrypt the data.
    158      * @param key The key.
    159      * @param bytes The data
    160      * @return byte[] The encrypted data
    161      * @throws HttpException If {@link Cipher.doFinal(byte[])} fails
    162      */
    163     private byte[] encrypt(byte[] key, byte[] bytes)
    164         throws AuthenticationException {
    165         Cipher ecipher = getCipher(key);
    166         try {
    167             byte[] enc = ecipher.doFinal(bytes);
    168             return enc;
    169         } catch (IllegalBlockSizeException e) {
    170             throw new AuthenticationException("Invalid block size for DES encryption.", e);
    171         } catch (BadPaddingException e) {
    172             throw new AuthenticationException("Data not padded correctly for DES encryption.", e);
    173         }
    174     }
    175
    176     /**
    177      * Prepares the object to create a response of the given length.
    178      * @param length the length of the response to prepare.
    179      */
    180     private void prepareResponse(int length) {
    181         currentResponse = new byte[length];
    182         currentPosition = 0;
    183     }
    184
    185     /**
    186      * Adds the given byte to the response.
    187      * @param b the byte to add.
    188      */
    189     private void addByte(byte b) {
    190         currentResponse[currentPosition] = b;
    191         currentPosition++;
    192     }
    193
    194     /**
    195      * Adds the given bytes to the response.
    196      * @param bytes the bytes to add.
    197      */
    198     private void addBytes(byte[] bytes) {
    199         for (int i = 0; i < bytes.length; i++) {
    200             currentResponse[currentPosition] = bytes[i];
    201             currentPosition++;
    202         }
    203     }
    204
    205     /**
    206      * Returns the response that has been generated after shrinking the array if
    207      * required and base64 encodes the response.
    208      * @return The response as above.
    209      */
    210     private String getResponse() {
    211         byte[] resp;
    212         if (currentResponse.length > currentPosition) {
    213             byte[] tmp = new byte[currentPosition];
    214             for (int i = 0; i < currentPosition; i++) {
    215                 tmp[i] = currentResponse[i];
    216             }
    217             resp = tmp;
    218         } else {
    219             resp = currentResponse;
    220         }
    221         return EncodingUtil.getAsciiString(Base64.encodeBase64(resp));
    222     }
    223
    224     /**
    225      * Creates the first message (type 1 message) in the NTLM authentication sequence.
    226      * This message includes the user name, domain and host for the authentication session.
    227      *
    228      * @param host the computer name of the host requesting authentication.
    229      * @param domain The domain to authenticate with.
    230      * @return String the message to add to the HTTP request header.
    231      */
    232     public String getType1Message(String host, String domain) {
    233         host = host.toUpperCase();
    234         domain = domain.toUpperCase();
    235         byte[] hostBytes = EncodingUtil.getBytes(host, DEFAULT_CHARSET);
    236         byte[] domainBytes = EncodingUtil.getBytes(domain, DEFAULT_CHARSET);
    237
    238         int finalLength = 32 + hostBytes.length + domainBytes.length;
    239         prepareResponse(finalLength);
    240
    241         // The initial id string.
    242         byte[] protocol = EncodingUtil.getBytes("NTLMSSP", DEFAULT_CHARSET);
    243         addBytes(protocol);
    244         addByte((byte) 0);
    245
    246         // Type
    247         addByte((byte) 1);
    248         addByte((byte) 0);
    249         addByte((byte) 0);
    250         addByte((byte) 0);
    251
    252         // Flags
    253         addByte((byte) 6);
    254         addByte((byte) 82);
    255         addByte((byte) 0);
    256         addByte((byte) 0);
    257
    258         // Domain length (first time).
    259         int iDomLen = domainBytes.length;
    260         byte[] domLen = convertShort(iDomLen);
    261         addByte(domLen[0]);
    262         addByte(domLen[1]);
    263
    264         // Domain length (second time).
    265         addByte(domLen[0]);
    266         addByte(domLen[1]);
    267
    268         // Domain offset.
    269         byte[] domOff = convertShort(hostBytes.length + 32);
    270         addByte(domOff[0]);
    271         addByte(domOff[1]);
    272         addByte((byte) 0);
    273         addByte((byte) 0);
    274
    275         // Host length (first time).
    276         byte[] hostLen = convertShort(hostBytes.length);
    277         addByte(hostLen[0]);
    278         addByte(hostLen[1]);
    279
    280         // Host length (second time).
    281         addByte(hostLen[0]);
    282         addByte(hostLen[1]);
    283
    284         // Host offset (always 32).
    285         byte[] hostOff = convertShort(32);
    286         addByte(hostOff[0]);
    287         addByte(hostOff[1]);
    288         addByte((byte) 0);
    289         addByte((byte) 0);
    290
    291         // Host String.
    292         addBytes(hostBytes);
    293
    294         // Domain String.
    295         addBytes(domainBytes);
    296
    297         return getResponse();
    298     }
    299
    300     /**
    301      * Extracts the server nonce out of the given message type 2.
    302      *
    303      * @param message the String containing the base64 encoded message.
    304      * @return an array of 8 bytes that the server sent to be used when
    305      * hashing the password.
    306      */
    307     public byte[] parseType2Message(String message) {
    308         // Decode the message first.
    309         byte[] msg = Base64.decodeBase64(EncodingUtil.getBytes(message, DEFAULT_CHARSET));
    310         byte[] nonce = new byte[8];
    311         // The nonce is the 8 bytes starting from the byte in position 24.
    312         for (int i = 0; i < 8; i++) {
    313             nonce[i] = msg[i + 24];
    314         }
    315         return nonce;
    316     }
    317
    318     /**
    319      * Creates the type 3 message using the given server nonce.  The type 3 message includes all the
    320      * information for authentication, host, domain, username and the result of encrypting the
    321      * nonce sent by the server using the user's password as the key.
    322      *
    323      * @param user The user name.  This should not include the domain name.
    324      * @param password The password.
    325      * @param host The host that is originating the authentication request.
    326      * @param domain The domain to authenticate within.
    327      * @param nonce the 8 byte array the server sent.
    328      * @return The type 3 message.
    329      * @throws AuthenticationException If {@encrypt(byte[],byte[])} fails.
    330      */
    331     public String getType3Message(String user, String password,
    332             String host, String domain, byte[] nonce)
    333     throws AuthenticationException {
    334
    335         int ntRespLen = 0;
    336         int lmRespLen = 24;
    337         domain = domain.toUpperCase();
    338         host = host.toUpperCase();
    339         user = user.toUpperCase();
    340         byte[] domainBytes = EncodingUtil.getBytes(domain, DEFAULT_CHARSET);
    341         byte[] hostBytes = EncodingUtil.getBytes(host, DEFAULT_CHARSET);
    342         byte[] userBytes = EncodingUtil.getBytes(user, credentialCharset);
    343         int domainLen = domainBytes.length;
    344         int hostLen = hostBytes.length;
    345         int userLen = userBytes.length;
    346         int finalLength = 64 + ntRespLen + lmRespLen + domainLen
    347             + userLen + hostLen;
    348         prepareResponse(finalLength);
    349         byte[] ntlmssp = EncodingUtil.getBytes("NTLMSSP", DEFAULT_CHARSET);
    350         addBytes(ntlmssp);
    351         addByte((byte) 0);
    352         addByte((byte) 3);
    353         addByte((byte) 0);
    354         addByte((byte) 0);
    355         addByte((byte) 0);
    356
    357         // LM Resp Length (twice)
    358         addBytes(convertShort(24));
    359         addBytes(convertShort(24));
    360
    361         // LM Resp Offset
    362         addBytes(convertShort(finalLength - 24));
    363         addByte((byte) 0);
    364         addByte((byte) 0);
    365
    366         // NT Resp Length (twice)
    367         addBytes(convertShort(0));
    368         addBytes(convertShort(0));
    369
    370         // NT Resp Offset
    371         addBytes(convertShort(finalLength));
    372         addByte((byte) 0);
    373         addByte((byte) 0);
    374
    375         // Domain length (twice)
    376         addBytes(convertShort(domainLen));
    377         addBytes(convertShort(domainLen));
    378
    379         // Domain offset.
    380         addBytes(convertShort(64));
    381         addByte((byte) 0);
    382         addByte((byte) 0);
    383
    384         // User Length (twice)
    385         addBytes(convertShort(userLen));
    386         addBytes(convertShort(userLen));
    387
    388         // User offset
    389         addBytes(convertShort(64 + domainLen));
    390         addByte((byte) 0);
    391         addByte((byte) 0);
    392
    393         // Host length (twice)
    394         addBytes(convertShort(hostLen));
    395         addBytes(convertShort(hostLen));
    396
    397         // Host offset
    398         addBytes(convertShort(64 + domainLen + userLen));
    399
    400         for (int i = 0; i < 6; i++) {
    401             addByte((byte) 0);
    402         }
    403
    404         // Message length
    405         addBytes(convertShort(finalLength));
    406         addByte((byte) 0);
    407         addByte((byte) 0);
    408
    409         // Flags
    410         addByte((byte) 6);
    411         addByte((byte) 82);
    412         addByte((byte) 0);
    413         addByte((byte) 0);
    414
    415         addBytes(domainBytes);
    416         addBytes(userBytes);
    417         addBytes(hostBytes);
    418         addBytes(hashPassword(password, nonce));
    419         return getResponse();
    420     }
    421
    422     /**
    423      * Creates the LANManager and NT response for the given password using the
    424      * given nonce.
    425      * @param password the password to create a hash for.
    426      * @param nonce the nonce sent by the server.
    427      * @return The response.
    428      * @throws HttpException If {@link #encrypt(byte[],byte[])} fails.
    429      */
    430     private byte[] hashPassword(String password, byte[] nonce)
    431         throws AuthenticationException {
    432         byte[] passw = EncodingUtil.getBytes(password.toUpperCase(), credentialCharset);
    433         byte[] lmPw1 = new byte[7];
    434         byte[] lmPw2 = new byte[7];
    435
    436         int len = passw.length;
    437         if (len > 7) {
    438             len = 7;
    439         }
    440
    441         int idx;
    442         for (idx = 0; idx < len; idx++) {
    443             lmPw1[idx] = passw[idx];
    444         }
    445         for (; idx < 7; idx++) {
    446             lmPw1[idx] = (byte) 0;
    447         }
    448
    449         len = passw.length;
    450         if (len > 14) {
    451             len = 14;
    452         }
    453         for (idx = 7; idx < len; idx++) {
    454             lmPw2[idx - 7] = passw[idx];
    455         }
    456         for (; idx < 14; idx++) {
    457             lmPw2[idx - 7] = (byte) 0;
    458         }
    459
    460         // Create LanManager hashed Password
    461         byte[] magic = {
    462             (byte) 0x4B, (byte) 0x47, (byte) 0x53, (byte) 0x21,
    463             (byte) 0x40, (byte) 0x23, (byte) 0x24, (byte) 0x25
    464         };
    465
    466         byte[] lmHpw1;
    467         lmHpw1 = encrypt(lmPw1, magic);
    468
    469         byte[] lmHpw2 = encrypt(lmPw2, magic);
    470
    471         byte[] lmHpw = new byte[21];
    472         for (int i = 0; i < lmHpw1.length; i++) {
    473             lmHpw[i] = lmHpw1[i];
    474         }
    475         for (int i = 0; i < lmHpw2.length; i++) {
    476             lmHpw[i + 8] = lmHpw2[i];
    477         }
    478         for (int i = 0; i < 5; i++) {
    479             lmHpw[i + 16] = (byte) 0;
    480         }
    481
    482         // Create the responses.
    483         byte[] lmResp = new byte[24];
    484         calcResp(lmHpw, nonce, lmResp);
    485
    486         return lmResp;
    487     }
    488
    489     /**
    490      * Takes a 21 byte array and treats it as 3 56-bit DES keys.  The 8 byte
    491      * plaintext is encrypted with each key and the resulting 24 bytes are
    492      * stored in the results array.
    493      *
    494      * @param keys The keys.
    495      * @param plaintext The plain text to encrypt.
    496      * @param results Where the results are stored.
    497      * @throws AuthenticationException If {@link #encrypt(byte[],byte[])} fails.
    498      */
    499     private void calcResp(byte[] keys, byte[] plaintext, byte[] results)
    500         throws AuthenticationException {
    501         byte[] keys1 = new byte[7];
    502         byte[] keys2 = new byte[7];
    503         byte[] keys3 = new byte[7];
    504         for (int i = 0; i < 7; i++) {
    505             keys1[i] = keys[i];
    506         }
    507
    508         for (int i = 0; i < 7; i++) {
    509             keys2[i] = keys[i + 7];
    510         }
    511
    512         for (int i = 0; i < 7; i++) {
    513             keys3[i] = keys[i + 14];
    514         }
    515         byte[] results1 = encrypt(keys1, plaintext);
    516
    517         byte[] results2 = encrypt(keys2, plaintext);
    518
    519         byte[] results3 = encrypt(keys3, plaintext);
    520
    521         for (int i = 0; i < 8; i++) {
    522             results[i] = results1[i];
    523         }
    524         for (int i = 0; i < 8; i++) {
    525             results[i + 8] = results2[i];
    526         }
    527         for (int i = 0; i < 8; i++) {
    528             results[i + 16] = results3[i];
    529         }
    530     }
    531
    532     /**
    533      * Converts a given number to a two byte array in little endian order.
    534      * @param num the number to convert.
    535      * @return The byte representation of <i>num</i> in little endian order.
    536      */
    537     private byte[] convertShort(int num) {
    538         byte[] val = new byte[2];
    539         String hex = Integer.toString(num, 16);
    540         while (hex.length() < 4) {
    541             hex = "0" + hex;
    542         }
    543         String low = hex.substring(2, 4);
    544         String high = hex.substring(0, 2);
    545
    546         val[0] = (byte) Integer.parseInt(low, 16);
    547         val[1] = (byte) Integer.parseInt(high, 16);
    548         return val;
    549     }
    550
    551     /**
    552      * @return Returns the credentialCharset.
    553      */
    554     public String getCredentialCharset() {
    555         return credentialCharset;
    556     }
    557
    558     /**
    559      * @param credentialCharset The credentialCharset to set.
    560      */
    561     public void setCredentialCharset(String credentialCharset) {
    562         this.credentialCharset = credentialCharset;
    563     }
    564
    565 }