1 /*
   2  * Copyright 2005-2008 Sun Microsystems, Inc.  All Rights Reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Sun designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Sun in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  22  * CA 95054 USA or visit www.sun.com if you need additional information or
  23  * have any questions.
  24  */
  25 
  26 package sun.net.www.protocol.http;
  27 
  28 import java.io.IOException;
  29 import java.io.UnsupportedEncodingException;
  30 import java.net.InetAddress;
  31 import java.net.PasswordAuthentication;
  32 import java.net.UnknownHostException;
  33 import java.net.URL;
  34 import java.security.GeneralSecurityException;
  35 import java.security.MessageDigest;
  36 import java.security.NoSuchAlgorithmException;
  37 import javax.crypto.Cipher;
  38 import javax.crypto.NoSuchPaddingException;
  39 import javax.crypto.SecretKey;
  40 import javax.crypto.SecretKeyFactory;
  41 import javax.crypto.spec.DESKeySpec;
  42 
  43 import sun.net.www.HeaderParser;
  44 
  45 /**
  46  * NTLMAuthentication:
  47  *
  48  * @author Michael McMahon
  49  */
  50 
  51 /*
  52  * NTLM authentication is nominally based on the framework defined in RFC2617,
  53  * but differs from the standard (Basic & Digest) schemes as follows:
  54  *
  55  * 1. A complete authentication requires three request/response transactions
  56  *    as shown below:
  57  *            REQ ------------------------------->
  58  *            <---- 401 (signalling NTLM) --------
  59  *
  60  *            REQ (with type1 NTLM msg) --------->
  61  *            <---- 401 (with type 2 NTLM msg) ---
  62  *
  63  *            REQ (with type3 NTLM msg) --------->
  64  *            <---- OK ---------------------------
  65  *
  66  * 2. The scope of the authentication is the TCP connection (which must be kept-alive)
  67  *    after the type2 response is received. This means that NTLM does not work end-to-end
  68  *    through a proxy, rather between client and proxy, or between client and server (with no proxy)
  69  */
  70 
  71 class NTLMAuthentication extends AuthenticationInfo {
  72     private static final long serialVersionUID = -2403849171106437142L;
  73 
  74     private byte[] type1;
  75     private byte[] type3;
  76 
  77     private SecretKeyFactory fac;
  78     private Cipher cipher;
  79     private MessageDigest md4;
  80     private String hostname;
  81     private static String defaultDomain; /* Domain to use if not specified by user */
  82 
  83     static {
  84         defaultDomain = java.security.AccessController.doPrivileged(
  85             new sun.security.action.GetPropertyAction("http.auth.ntlm.domain",
  86                                                       "domain"));
  87     };
  88 
  89     static boolean supportsTransparentAuth () {
  90         return false;
  91     }
  92 
  93     private void init0() {
  94         type1 = new byte[256];
  95         type3 = new byte[256];
  96         System.arraycopy (new byte[] {'N','T','L','M','S','S','P',0,1}, 0, type1, 0, 9);
  97         type1[12] = (byte) 3;
  98         type1[13] = (byte) 0xb2;
  99         type1[28] = (byte) 0x20;
 100         System.arraycopy (new byte[] {'N','T','L','M','S','S','P',0,3}, 0, type3, 0, 9);
 101         type3[12] = (byte) 0x18;
 102         type3[14] = (byte) 0x18;
 103         type3[20] = (byte) 0x18;
 104         type3[22] = (byte) 0x18;
 105         type3[32] = (byte) 0x40;
 106         type3[60] = (byte) 1;
 107         type3[61] = (byte) 0x82;
 108 
 109         try {
 110             hostname = java.security.AccessController.doPrivileged(
 111                 new java.security.PrivilegedAction<String>() {
 112                 public String run() {
 113                     String localhost;
 114                     try {
 115                         localhost = InetAddress.getLocalHost().getHostName().toUpperCase();
 116                     } catch (UnknownHostException e) {
 117                          localhost = "localhost";
 118                     }
 119                     return localhost;
 120                 }
 121             });
 122             int x = hostname.indexOf ('.');
 123             if (x != -1) {
 124                 hostname = hostname.substring (0, x);
 125             }
 126             fac = SecretKeyFactory.getInstance ("DES");
 127             cipher = Cipher.getInstance ("DES/ECB/NoPadding");
 128             md4 = sun.security.provider.MD4.getInstance();
 129         } catch (NoSuchPaddingException e) {
 130             assert false;
 131         } catch (NoSuchAlgorithmException e) {
 132             assert false;
 133         }
 134     };
 135 
 136     PasswordAuthentication pw;
 137     String username;
 138     String ntdomain;
 139     String password;
 140 
 141     /**
 142      * Create a NTLMAuthentication:
 143      * Username may be specified as domain<BACKSLASH>username in the application Authenticator.
 144      * If this notation is not used, then the domain will be taken
 145      * from a system property: "http.auth.ntlm.domain".
 146      */
 147     public NTLMAuthentication(boolean isProxy, URL url, PasswordAuthentication pw) {
 148         super(isProxy ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION,
 149                 AuthScheme.NTLM,
 150                 url,
 151                 "");
 152         init (pw);
 153     }
 154 
 155     private void init (PasswordAuthentication pw) {
 156         this.pw = pw;
 157         String s = pw.getUserName();
 158         int i = s.indexOf ('\\');
 159         if (i == -1) {
 160             username = s;
 161             ntdomain = defaultDomain;
 162         } else {
 163             ntdomain = s.substring (0, i).toUpperCase();
 164             username = s.substring (i+1);
 165         }
 166         password = new String (pw.getPassword());
 167         init0();
 168     }
 169 
 170    /**
 171     * Constructor used for proxy entries
 172     */
 173     public NTLMAuthentication(boolean isProxy, String host, int port,
 174                                 PasswordAuthentication pw) {
 175         super(isProxy ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION,
 176                 AuthScheme.NTLM,
 177                 host,
 178                 port,
 179                 "");
 180         init (pw);
 181     }
 182 
 183     /**
 184      * @return true if this authentication supports preemptive authorization
 185      */
 186     boolean supportsPreemptiveAuthorization() {
 187         return false;
 188     }
 189 
 190     /**
 191      * @return the name of the HTTP header this authentication wants set
 192      */
 193     String getHeaderName() {
 194         if (type == SERVER_AUTHENTICATION) {
 195             return "Authorization";
 196         } else {
 197             return "Proxy-authorization";
 198         }
 199     }
 200 
 201     /**
 202      * Not supported. Must use the setHeaders() method
 203      */
 204     String getHeaderValue(URL url, String method) {
 205         throw new RuntimeException ("getHeaderValue not supported");
 206     }
 207 
 208     /**
 209      * Check if the header indicates that the current auth. parameters are stale.
 210      * If so, then replace the relevant field with the new value
 211      * and return true. Otherwise return false.
 212      * returning true means the request can be retried with the same userid/password
 213      * returning false means we have to go back to the user to ask for a new
 214      * username password.
 215      */
 216     boolean isAuthorizationStale (String header) {
 217         return false; /* should not be called for ntlm */
 218     }
 219 
 220     /**
 221      * Set header(s) on the given connection.
 222      * @param conn The connection to apply the header(s) to
 223      * @param p A source of header values for this connection, not used because
 224      *          HeaderParser converts the fields to lower case, use raw instead
 225      * @param raw The raw header field.
 226      * @return true if all goes well, false if no headers were set.
 227      */
 228     synchronized boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
 229 
 230         try {
 231             String response;
 232             if (raw.length() < 6) { /* NTLM<sp> */
 233                 response = buildType1Msg ();
 234             } else {
 235                 String msg = raw.substring (5); /* skip NTLM<sp> */
 236                 response = buildType3Msg (msg);
 237             }
 238             conn.setAuthenticationProperty(getHeaderName(), response);
 239             return true;
 240         } catch (IOException e) {
 241             return false;
 242         } catch (GeneralSecurityException e) {
 243             return false;
 244         }
 245     }
 246 
 247     private void copybytes (byte[] dest, int destpos, String src, String enc) {
 248         try {
 249             byte[] x = src.getBytes(enc);
 250             System.arraycopy (x, 0, dest, destpos, x.length);
 251         } catch (UnsupportedEncodingException e) {
 252             assert false;
 253         }
 254     }
 255 
 256     private String buildType1Msg () {
 257         int dlen = ntdomain.length();
 258         type1[16]= (byte) (dlen % 256);
 259         type1[17]= (byte) (dlen / 256);
 260         type1[18] = type1[16];
 261         type1[19] = type1[17];
 262 
 263         int hlen = hostname.length();
 264         type1[24]= (byte) (hlen % 256);
 265         type1[25]= (byte) (hlen / 256);
 266         type1[26] = type1[24];
 267         type1[27] = type1[25];
 268 
 269         copybytes (type1, 32, hostname, "ISO8859_1");
 270         copybytes (type1, hlen+32, ntdomain, "ISO8859_1");
 271         type1[20] = (byte) ((hlen+32) % 256);
 272         type1[21] = (byte) ((hlen+32) / 256);
 273 
 274         byte[] msg = new byte [32 + hlen + dlen];
 275         System.arraycopy (type1, 0, msg, 0, 32 + hlen + dlen);
 276         String result = "NTLM " + (new B64Encoder()).encode (msg);
 277         return result;
 278     }
 279 
 280 
 281     /* Convert a 7 byte array to an 8 byte array (for a des key with parity)
 282      * input starts at offset off
 283      */
 284     private byte[] makeDesKey (byte[] input, int off) {
 285         int[] in = new int [input.length];
 286         for (int i=0; i<in.length; i++ ) {
 287             in[i] = input[i]<0 ? input[i]+256: input[i];
 288         }
 289         byte[] out = new byte[8];
 290         out[0] = (byte)in[off+0];
 291         out[1] = (byte)(((in[off+0] << 7) & 0xFF) | (in[off+1] >> 1));
 292         out[2] = (byte)(((in[off+1] << 6) & 0xFF) | (in[off+2] >> 2));
 293         out[3] = (byte)(((in[off+2] << 5) & 0xFF) | (in[off+3] >> 3));
 294         out[4] = (byte)(((in[off+3] << 4) & 0xFF) | (in[off+4] >> 4));
 295         out[5] = (byte)(((in[off+4] << 3) & 0xFF) | (in[off+5] >> 5));
 296         out[6] = (byte)(((in[off+5] << 2) & 0xFF) | (in[off+6] >> 6));
 297         out[7] = (byte)((in[off+6] << 1) & 0xFF);
 298         return out;
 299     }
 300 
 301     private byte[] calcLMHash () throws GeneralSecurityException {
 302         byte[] magic = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25};
 303         byte[] pwb = password.toUpperCase ().getBytes();
 304         byte[] pwb1 = new byte [14];
 305         int len = password.length();
 306         if (len > 14)
 307             len = 14;
 308         System.arraycopy (pwb, 0, pwb1, 0, len); /* Zero padded */
 309 
 310         DESKeySpec dks1 = new DESKeySpec (makeDesKey (pwb1, 0));
 311         DESKeySpec dks2 = new DESKeySpec (makeDesKey (pwb1, 7));
 312 
 313         SecretKey key1 = fac.generateSecret (dks1);
 314         SecretKey key2 = fac.generateSecret (dks2);
 315         cipher.init (Cipher.ENCRYPT_MODE, key1);
 316         byte[] out1 = cipher.doFinal (magic, 0, 8);
 317         cipher.init (Cipher.ENCRYPT_MODE, key2);
 318         byte[] out2 = cipher.doFinal (magic, 0, 8);
 319 
 320         byte[] result = new byte [21];
 321         System.arraycopy (out1, 0, result, 0, 8);
 322         System.arraycopy (out2, 0, result, 8, 8);
 323         return result;
 324     }
 325 
 326     private byte[] calcNTHash () throws GeneralSecurityException {
 327         byte[] pw = null;
 328         try {
 329             pw = password.getBytes ("UnicodeLittleUnmarked");
 330         } catch (UnsupportedEncodingException e) {
 331             assert false;
 332         }
 333         byte[] out = md4.digest (pw);
 334         byte[] result = new byte [21];
 335         System.arraycopy (out, 0, result, 0, 16);
 336         return result;
 337     }
 338 
 339     /* key is a 21 byte array. Split it into 3 7 byte chunks,
 340      * Convert each to 8 byte DES keys, encrypt the text arg with
 341      * each key and return the three results in a sequential []
 342      */
 343     private byte[] calcResponse (byte[] key, byte[] text)
 344     throws GeneralSecurityException {
 345         assert key.length == 21;
 346         DESKeySpec dks1 = new DESKeySpec (makeDesKey (key, 0));
 347         DESKeySpec dks2 = new DESKeySpec (makeDesKey (key, 7));
 348         DESKeySpec dks3 = new DESKeySpec (makeDesKey (key, 14));
 349         SecretKey key1 = fac.generateSecret (dks1);
 350         SecretKey key2 = fac.generateSecret (dks2);
 351         SecretKey key3 = fac.generateSecret (dks3);
 352         cipher.init (Cipher.ENCRYPT_MODE, key1);
 353         byte[] out1 = cipher.doFinal (text, 0, 8);
 354         cipher.init (Cipher.ENCRYPT_MODE, key2);
 355         byte[] out2 = cipher.doFinal (text, 0, 8);
 356         cipher.init (Cipher.ENCRYPT_MODE, key3);
 357         byte[] out3 = cipher.doFinal (text, 0, 8);
 358         byte[] result = new byte [24];
 359         System.arraycopy (out1, 0, result, 0, 8);
 360         System.arraycopy (out2, 0, result, 8, 8);
 361         System.arraycopy (out3, 0, result, 16, 8);
 362         return result;
 363     }
 364 
 365     private String buildType3Msg (String challenge) throws GeneralSecurityException,
 366                                                            IOException  {
 367         /* First decode the type2 message to get the server nonce */
 368         /* nonce is located at type2[24] for 8 bytes */
 369 
 370         byte[] type2 = (new sun.misc.BASE64Decoder()).decodeBuffer (challenge);
 371         byte[] nonce = new byte [8];
 372         System.arraycopy (type2, 24, nonce, 0, 8);
 373 
 374         int ulen = username.length()*2;
 375         type3[36] = type3[38] = (byte) (ulen % 256);
 376         type3[37] = type3[39] = (byte) (ulen / 256);
 377         int dlen = ntdomain.length()*2;
 378         type3[28] = type3[30] = (byte) (dlen % 256);
 379         type3[29] = type3[31] = (byte) (dlen / 256);
 380         int hlen = hostname.length()*2;
 381         type3[44] = type3[46] = (byte) (hlen % 256);
 382         type3[45] = type3[47] = (byte) (hlen / 256);
 383 
 384         int l = 64;
 385         copybytes (type3, l, ntdomain, "UnicodeLittleUnmarked");
 386         type3[32] = (byte) (l % 256);
 387         type3[33] = (byte) (l / 256);
 388         l += dlen;
 389         copybytes (type3, l, username, "UnicodeLittleUnmarked");
 390         type3[40] = (byte) (l % 256);
 391         type3[41] = (byte) (l / 256);
 392         l += ulen;
 393         copybytes (type3, l, hostname, "UnicodeLittleUnmarked");
 394         type3[48] = (byte) (l % 256);
 395         type3[49] = (byte) (l / 256);
 396         l += hlen;
 397 
 398         byte[] lmhash = calcLMHash();
 399         byte[] lmresponse = calcResponse (lmhash, nonce);
 400         byte[] nthash = calcNTHash();
 401         byte[] ntresponse = calcResponse (nthash, nonce);
 402         System.arraycopy (lmresponse, 0, type3, l, 24);
 403         type3[16] = (byte) (l % 256);
 404         type3[17] = (byte) (l / 256);
 405         l += 24;
 406         System.arraycopy (ntresponse, 0, type3, l, 24);
 407         type3[24] = (byte) (l % 256);
 408         type3[25] = (byte) (l / 256);
 409         l += 24;
 410         type3[56] = (byte) (l % 256);
 411         type3[57] = (byte) (l / 256);
 412 
 413         byte[] msg = new byte [l];
 414         System.arraycopy (type3, 0, msg, 0, l);
 415         String result = "NTLM " + (new B64Encoder()).encode (msg);
 416         return result;
 417     }
 418 
 419 }
 420 
 421 
 422 class B64Encoder extends sun.misc.BASE64Encoder {
 423     /* to force it to to the entire encoding in one line */
 424     protected int bytesPerLine () {
 425         return 1024;
 426     }
 427 }