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