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 }