jdk/src/solaris/classes/sun/net/www/protocol/http/NTLMAuthentication.java
changeset 7178 03d19396adb9
parent 7177 0113db4feebc
parent 7176 19571999a46a
child 7179 4afb81e50183
child 7180 71cc1d6d7c4d
equal deleted inserted replaced
7177:0113db4feebc 7178:03d19396adb9
     1 /*
       
     2  * Copyright (c) 2005, 2008, Oracle and/or its affiliates. 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.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * 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 }