| /* |
| * Copyright 2005-2008 Sun Microsystems, Inc. All Rights Reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Sun designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Sun in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| * CA 95054 USA or visit www.sun.com if you need additional information or |
| * have any questions. |
| */ |
| |
| package sun.net.www.protocol.http; |
| |
| import java.util.Arrays; |
| import java.util.StringTokenizer; |
| import java.util.Random; |
| |
| import sun.net.www.HeaderParser; |
| |
| import java.io.*; |
| import javax.crypto.*; |
| import javax.crypto.spec.*; |
| import java.security.*; |
| import java.net.*; |
| |
| /** |
| * NTLMAuthentication: |
| * |
| * @author Michael McMahon |
| */ |
| |
| /* |
| * NTLM authentication is nominally based on the framework defined in RFC2617, |
| * but differs from the standard (Basic & Digest) schemes as follows: |
| * |
| * 1. A complete authentication requires three request/response transactions |
| * as shown below: |
| * REQ -------------------------------> |
| * <---- 401 (signalling NTLM) -------- |
| * |
| * REQ (with type1 NTLM msg) ---------> |
| * <---- 401 (with type 2 NTLM msg) --- |
| * |
| * REQ (with type3 NTLM msg) ---------> |
| * <---- OK --------------------------- |
| * |
| * 2. The scope of the authentication is the TCP connection (which must be kept-alive) |
| * after the type2 response is received. This means that NTLM does not work end-to-end |
| * through a proxy, rather between client and proxy, or between client and server (with no proxy) |
| */ |
| |
| class NTLMAuthentication extends AuthenticationInfo { |
| private static final long serialVersionUID = -2403849171106437142L; |
| |
| static char NTLM_AUTH = 'N'; |
| |
| private byte[] type1; |
| private byte[] type3; |
| |
| private SecretKeyFactory fac; |
| private Cipher cipher; |
| private MessageDigest md4; |
| private String hostname; |
| private static String defaultDomain; /* Domain to use if not specified by user */ |
| |
| static { |
| defaultDomain = java.security.AccessController.doPrivileged( |
| new sun.security.action.GetPropertyAction("http.auth.ntlm.domain", |
| "domain")); |
| }; |
| |
| static boolean supportsTransparentAuth () { |
| return false; |
| } |
| |
| private void init0() { |
| type1 = new byte[256]; |
| type3 = new byte[256]; |
| System.arraycopy (new byte[] {'N','T','L','M','S','S','P',0,1}, 0, type1, 0, 9); |
| type1[12] = (byte) 3; |
| type1[13] = (byte) 0xb2; |
| type1[28] = (byte) 0x20; |
| System.arraycopy (new byte[] {'N','T','L','M','S','S','P',0,3}, 0, type3, 0, 9); |
| type3[12] = (byte) 0x18; |
| type3[14] = (byte) 0x18; |
| type3[20] = (byte) 0x18; |
| type3[22] = (byte) 0x18; |
| type3[32] = (byte) 0x40; |
| type3[60] = (byte) 1; |
| type3[61] = (byte) 0x82; |
| |
| try { |
| hostname = java.security.AccessController.doPrivileged( |
| new java.security.PrivilegedAction<String>() { |
| public String run() { |
| String localhost; |
| try { |
| localhost = InetAddress.getLocalHost().getHostName().toUpperCase(); |
| } catch (UnknownHostException e) { |
| localhost = "localhost"; |
| } |
| return localhost; |
| } |
| }); |
| int x = hostname.indexOf ('.'); |
| if (x != -1) { |
| hostname = hostname.substring (0, x); |
| } |
| fac = SecretKeyFactory.getInstance ("DES"); |
| cipher = Cipher.getInstance ("DES/ECB/NoPadding"); |
| md4 = sun.security.provider.MD4.getInstance(); |
| } catch (NoSuchPaddingException e) { |
| assert false; |
| } catch (NoSuchAlgorithmException e) { |
| assert false; |
| } |
| }; |
| |
| PasswordAuthentication pw; |
| String username; |
| String ntdomain; |
| String password; |
| |
| /** |
| * Create a NTLMAuthentication: |
| * Username may be specified as domain<BACKSLASH>username in the application Authenticator. |
| * If this notation is not used, then the domain will be taken |
| * from a system property: "http.auth.ntlm.domain". |
| */ |
| public NTLMAuthentication(boolean isProxy, URL url, PasswordAuthentication pw) { |
| super(isProxy?PROXY_AUTHENTICATION:SERVER_AUTHENTICATION, NTLM_AUTH, url, ""); |
| init (pw); |
| } |
| |
| private void init (PasswordAuthentication pw) { |
| this.pw = pw; |
| String s = pw.getUserName(); |
| int i = s.indexOf ('\\'); |
| if (i == -1) { |
| username = s; |
| ntdomain = defaultDomain; |
| } else { |
| ntdomain = s.substring (0, i).toUpperCase(); |
| username = s.substring (i+1); |
| } |
| password = new String (pw.getPassword()); |
| init0(); |
| } |
| |
| /** |
| * Constructor used for proxy entries |
| */ |
| public NTLMAuthentication(boolean isProxy, String host, int port, |
| PasswordAuthentication pw) { |
| super(isProxy?PROXY_AUTHENTICATION:SERVER_AUTHENTICATION, NTLM_AUTH,host, port, ""); |
| init (pw); |
| } |
| |
| /** |
| * @return true if this authentication supports preemptive authorization |
| */ |
| boolean supportsPreemptiveAuthorization() { |
| return false; |
| } |
| |
| /** |
| * @return the name of the HTTP header this authentication wants set |
| */ |
| String getHeaderName() { |
| if (type == SERVER_AUTHENTICATION) { |
| return "Authorization"; |
| } else { |
| return "Proxy-authorization"; |
| } |
| } |
| |
| /** |
| * Not supported. Must use the setHeaders() method |
| */ |
| String getHeaderValue(URL url, String method) { |
| throw new RuntimeException ("getHeaderValue not supported"); |
| } |
| |
| /** |
| * Check if the header indicates that the current auth. parameters are stale. |
| * If so, then replace the relevant field with the new value |
| * and return true. Otherwise return false. |
| * returning true means the request can be retried with the same userid/password |
| * returning false means we have to go back to the user to ask for a new |
| * username password. |
| */ |
| boolean isAuthorizationStale (String header) { |
| return false; /* should not be called for ntlm */ |
| } |
| |
| /** |
| * Set header(s) on the given connection. |
| * @param conn The connection to apply the header(s) to |
| * @param p A source of header values for this connection, not used because |
| * HeaderParser converts the fields to lower case, use raw instead |
| * @param raw The raw header field. |
| * @return true if all goes well, false if no headers were set. |
| */ |
| synchronized boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) { |
| |
| try { |
| String response; |
| if (raw.length() < 6) { /* NTLM<sp> */ |
| response = buildType1Msg (); |
| } else { |
| String msg = raw.substring (5); /* skip NTLM<sp> */ |
| response = buildType3Msg (msg); |
| } |
| conn.setAuthenticationProperty(getHeaderName(), response); |
| return true; |
| } catch (IOException e) { |
| return false; |
| } catch (GeneralSecurityException e) { |
| return false; |
| } |
| } |
| |
| /* This is a no-op for NTLM, because there is no authentication information |
| * provided by the server to the client |
| */ |
| public void checkResponse (String header, String method, URL url) throws IOException { |
| } |
| |
| |
| private void copybytes (byte[] dest, int destpos, String src, String enc) { |
| try { |
| byte[] x = src.getBytes(enc); |
| System.arraycopy (x, 0, dest, destpos, x.length); |
| } catch (UnsupportedEncodingException e) { |
| assert false; |
| } |
| } |
| |
| private String buildType1Msg () { |
| int dlen = ntdomain.length(); |
| type1[16]= (byte) (dlen % 256); |
| type1[17]= (byte) (dlen / 256); |
| type1[18] = type1[16]; |
| type1[19] = type1[17]; |
| |
| int hlen = hostname.length(); |
| type1[24]= (byte) (hlen % 256); |
| type1[25]= (byte) (hlen / 256); |
| type1[26] = type1[24]; |
| type1[27] = type1[25]; |
| |
| copybytes (type1, 32, hostname, "ISO8859_1"); |
| copybytes (type1, hlen+32, ntdomain, "ISO8859_1"); |
| type1[20] = (byte) ((hlen+32) % 256); |
| type1[21] = (byte) ((hlen+32) / 256); |
| |
| byte[] msg = new byte [32 + hlen + dlen]; |
| System.arraycopy (type1, 0, msg, 0, 32 + hlen + dlen); |
| String result = "NTLM " + (new B64Encoder()).encode (msg); |
| return result; |
| } |
| |
| |
| /* Convert a 7 byte array to an 8 byte array (for a des key with parity) |
| * input starts at offset off |
| */ |
| private byte[] makeDesKey (byte[] input, int off) { |
| int[] in = new int [input.length]; |
| for (int i=0; i<in.length; i++ ) { |
| in[i] = input[i]<0 ? input[i]+256: input[i]; |
| } |
| byte[] out = new byte[8]; |
| out[0] = (byte)in[off+0]; |
| out[1] = (byte)(((in[off+0] << 7) & 0xFF) | (in[off+1] >> 1)); |
| out[2] = (byte)(((in[off+1] << 6) & 0xFF) | (in[off+2] >> 2)); |
| out[3] = (byte)(((in[off+2] << 5) & 0xFF) | (in[off+3] >> 3)); |
| out[4] = (byte)(((in[off+3] << 4) & 0xFF) | (in[off+4] >> 4)); |
| out[5] = (byte)(((in[off+4] << 3) & 0xFF) | (in[off+5] >> 5)); |
| out[6] = (byte)(((in[off+5] << 2) & 0xFF) | (in[off+6] >> 6)); |
| out[7] = (byte)((in[off+6] << 1) & 0xFF); |
| return out; |
| } |
| |
| private byte[] calcLMHash () throws GeneralSecurityException { |
| byte[] magic = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25}; |
| byte[] pwb = password.toUpperCase ().getBytes(); |
| byte[] pwb1 = new byte [14]; |
| int len = password.length(); |
| if (len > 14) |
| len = 14; |
| System.arraycopy (pwb, 0, pwb1, 0, len); /* Zero padded */ |
| |
| DESKeySpec dks1 = new DESKeySpec (makeDesKey (pwb1, 0)); |
| DESKeySpec dks2 = new DESKeySpec (makeDesKey (pwb1, 7)); |
| |
| SecretKey key1 = fac.generateSecret (dks1); |
| SecretKey key2 = fac.generateSecret (dks2); |
| cipher.init (Cipher.ENCRYPT_MODE, key1); |
| byte[] out1 = cipher.doFinal (magic, 0, 8); |
| cipher.init (Cipher.ENCRYPT_MODE, key2); |
| byte[] out2 = cipher.doFinal (magic, 0, 8); |
| |
| byte[] result = new byte [21]; |
| System.arraycopy (out1, 0, result, 0, 8); |
| System.arraycopy (out2, 0, result, 8, 8); |
| return result; |
| } |
| |
| private byte[] calcNTHash () throws GeneralSecurityException { |
| byte[] pw = null; |
| try { |
| pw = password.getBytes ("UnicodeLittleUnmarked"); |
| } catch (UnsupportedEncodingException e) { |
| assert false; |
| } |
| byte[] out = md4.digest (pw); |
| byte[] result = new byte [21]; |
| System.arraycopy (out, 0, result, 0, 16); |
| return result; |
| } |
| |
| /* key is a 21 byte array. Split it into 3 7 byte chunks, |
| * Convert each to 8 byte DES keys, encrypt the text arg with |
| * each key and return the three results in a sequential [] |
| */ |
| private byte[] calcResponse (byte[] key, byte[] text) |
| throws GeneralSecurityException { |
| assert key.length == 21; |
| DESKeySpec dks1 = new DESKeySpec (makeDesKey (key, 0)); |
| DESKeySpec dks2 = new DESKeySpec (makeDesKey (key, 7)); |
| DESKeySpec dks3 = new DESKeySpec (makeDesKey (key, 14)); |
| SecretKey key1 = fac.generateSecret (dks1); |
| SecretKey key2 = fac.generateSecret (dks2); |
| SecretKey key3 = fac.generateSecret (dks3); |
| cipher.init (Cipher.ENCRYPT_MODE, key1); |
| byte[] out1 = cipher.doFinal (text, 0, 8); |
| cipher.init (Cipher.ENCRYPT_MODE, key2); |
| byte[] out2 = cipher.doFinal (text, 0, 8); |
| cipher.init (Cipher.ENCRYPT_MODE, key3); |
| byte[] out3 = cipher.doFinal (text, 0, 8); |
| byte[] result = new byte [24]; |
| System.arraycopy (out1, 0, result, 0, 8); |
| System.arraycopy (out2, 0, result, 8, 8); |
| System.arraycopy (out3, 0, result, 16, 8); |
| return result; |
| } |
| |
| private String buildType3Msg (String challenge) throws GeneralSecurityException, |
| IOException { |
| /* First decode the type2 message to get the server nonce */ |
| /* nonce is located at type2[24] for 8 bytes */ |
| |
| byte[] type2 = (new sun.misc.BASE64Decoder()).decodeBuffer (challenge); |
| byte[] nonce = new byte [8]; |
| System.arraycopy (type2, 24, nonce, 0, 8); |
| |
| int ulen = username.length()*2; |
| type3[36] = type3[38] = (byte) (ulen % 256); |
| type3[37] = type3[39] = (byte) (ulen / 256); |
| int dlen = ntdomain.length()*2; |
| type3[28] = type3[30] = (byte) (dlen % 256); |
| type3[29] = type3[31] = (byte) (dlen / 256); |
| int hlen = hostname.length()*2; |
| type3[44] = type3[46] = (byte) (hlen % 256); |
| type3[45] = type3[47] = (byte) (hlen / 256); |
| |
| int l = 64; |
| copybytes (type3, l, ntdomain, "UnicodeLittleUnmarked"); |
| type3[32] = (byte) (l % 256); |
| type3[33] = (byte) (l / 256); |
| l += dlen; |
| copybytes (type3, l, username, "UnicodeLittleUnmarked"); |
| type3[40] = (byte) (l % 256); |
| type3[41] = (byte) (l / 256); |
| l += ulen; |
| copybytes (type3, l, hostname, "UnicodeLittleUnmarked"); |
| type3[48] = (byte) (l % 256); |
| type3[49] = (byte) (l / 256); |
| l += hlen; |
| |
| byte[] lmhash = calcLMHash(); |
| byte[] lmresponse = calcResponse (lmhash, nonce); |
| byte[] nthash = calcNTHash(); |
| byte[] ntresponse = calcResponse (nthash, nonce); |
| System.arraycopy (lmresponse, 0, type3, l, 24); |
| type3[16] = (byte) (l % 256); |
| type3[17] = (byte) (l / 256); |
| l += 24; |
| System.arraycopy (ntresponse, 0, type3, l, 24); |
| type3[24] = (byte) (l % 256); |
| type3[25] = (byte) (l / 256); |
| l += 24; |
| type3[56] = (byte) (l % 256); |
| type3[57] = (byte) (l / 256); |
| |
| byte[] msg = new byte [l]; |
| System.arraycopy (type3, 0, msg, 0, l); |
| String result = "NTLM " + (new B64Encoder()).encode (msg); |
| return result; |
| } |
| |
| } |
| |
| |
| class B64Encoder extends sun.misc.BASE64Encoder { |
| /* to force it to to the entire encoding in one line */ |
| protected int bytesPerLine () { |
| return 1024; |
| } |
| } |