1/* 2 * Copyright (c) 2005, 2016, 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 26package sun.net.www.protocol.http.ntlm; 27 28import com.sun.security.ntlm.Client; 29import com.sun.security.ntlm.NTLMException; 30import java.io.IOException; 31import java.net.InetAddress; 32import java.net.PasswordAuthentication; 33import java.net.UnknownHostException; 34import java.net.URL; 35import java.security.GeneralSecurityException; 36import java.util.Base64; 37import java.util.Objects; 38import java.util.Properties; 39 40import sun.net.www.HeaderParser; 41import sun.net.www.protocol.http.AuthenticationInfo; 42import sun.net.www.protocol.http.AuthScheme; 43import sun.net.www.protocol.http.HttpURLConnection; 44import sun.security.action.GetPropertyAction; 45 46/** 47 * NTLMAuthentication: 48 * 49 * @author Michael McMahon 50 */ 51 52/* 53 * NTLM authentication is nominally based on the framework defined in RFC2617, 54 * but differs from the standard (Basic & Digest) schemes as follows: 55 * 56 * 1. A complete authentication requires three request/response transactions 57 * as shown below: 58 * REQ -------------------------------> 59 * <---- 401 (signalling NTLM) -------- 60 * 61 * REQ (with type1 NTLM msg) ---------> 62 * <---- 401 (with type 2 NTLM msg) --- 63 * 64 * REQ (with type3 NTLM msg) ---------> 65 * <---- OK --------------------------- 66 * 67 * 2. The scope of the authentication is the TCP connection (which must be kept-alive) 68 * after the type2 response is received. This means that NTLM does not work end-to-end 69 * through a proxy, rather between client and proxy, or between client and server (with no proxy) 70 */ 71 72public class NTLMAuthentication extends AuthenticationInfo { 73 private static final long serialVersionUID = 170L; 74 75 private static final NTLMAuthenticationCallback NTLMAuthCallback = 76 NTLMAuthenticationCallback.getNTLMAuthenticationCallback(); 77 78 private String hostname; 79 /* Domain to use if not specified by user */ 80 private static final String defaultDomain; 81 /* Whether cache is enabled for NTLM */ 82 private static final boolean ntlmCache; 83 static { 84 Properties props = GetPropertyAction.privilegedGetProperties(); 85 defaultDomain = props.getProperty("http.auth.ntlm.domain", ""); 86 String ntlmCacheProp = props.getProperty("jdk.ntlm.cache", "true"); 87 ntlmCache = Boolean.parseBoolean(ntlmCacheProp); 88 } 89 90 public static boolean supportsTransparentAuth () { 91 return false; 92 } 93 94 /** 95 * Returns true if the given site is trusted, i.e. we can try 96 * transparent Authentication. 97 */ 98 public static boolean isTrustedSite(URL url) { 99 return NTLMAuthCallback.isTrustedSite(url); 100 } 101 102 private void init0() { 103 104 hostname = java.security.AccessController.doPrivileged( 105 new java.security.PrivilegedAction<>() { 106 public String run() { 107 String localhost; 108 try { 109 localhost = InetAddress.getLocalHost().getHostName(); 110 } catch (UnknownHostException e) { 111 localhost = "localhost"; 112 } 113 return localhost; 114 } 115 }); 116 }; 117 118 PasswordAuthentication pw; 119 120 Client client; 121 /** 122 * Create a NTLMAuthentication: 123 * Username may be specified as {@literal domain<BACKSLASH>username} 124 * in the application Authenticator. 125 * If this notation is not used, then the domain will be taken 126 * from a system property: "http.auth.ntlm.domain". 127 */ 128 public NTLMAuthentication(boolean isProxy, URL url, PasswordAuthentication pw, 129 String authenticatorKey) { 130 super(isProxy ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION, 131 AuthScheme.NTLM, 132 url, 133 "", 134 Objects.requireNonNull(authenticatorKey)); 135 init (pw); 136 } 137 138 private void init (PasswordAuthentication pw) { 139 String username; 140 String ntdomain; 141 char[] password; 142 this.pw = pw; 143 String s = pw.getUserName(); 144 int i = s.indexOf ('\\'); 145 if (i == -1) { 146 username = s; 147 ntdomain = defaultDomain; 148 } else { 149 ntdomain = s.substring (0, i).toUpperCase(); 150 username = s.substring (i+1); 151 } 152 password = pw.getPassword(); 153 init0(); 154 try { 155 String version = GetPropertyAction.privilegedGetProperty("ntlm.version"); 156 client = new Client(version, hostname, username, ntdomain, password); 157 } catch (NTLMException ne) { 158 try { 159 client = new Client(null, hostname, username, ntdomain, password); 160 } catch (NTLMException ne2) { 161 // Will never happen 162 throw new AssertionError("Really?"); 163 } 164 } 165 } 166 167 /** 168 * Constructor used for proxy entries 169 */ 170 public NTLMAuthentication(boolean isProxy, String host, int port, 171 PasswordAuthentication pw, 172 String authenticatorKey) { 173 super(isProxy ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION, 174 AuthScheme.NTLM, 175 host, 176 port, 177 "", 178 Objects.requireNonNull(authenticatorKey)); 179 init (pw); 180 } 181 182 @Override 183 protected boolean useAuthCache() { 184 return ntlmCache && super.useAuthCache(); 185 } 186 187 /** 188 * @return true if this authentication supports preemptive authorization 189 */ 190 @Override 191 public boolean supportsPreemptiveAuthorization() { 192 return false; 193 } 194 195 /** 196 * Not supported. Must use the setHeaders() method 197 */ 198 @Override 199 public String getHeaderValue(URL url, String method) { 200 throw new RuntimeException ("getHeaderValue not supported"); 201 } 202 203 /** 204 * Check if the header indicates that the current auth. parameters are stale. 205 * If so, then replace the relevant field with the new value 206 * and return true. Otherwise return false. 207 * returning true means the request can be retried with the same userid/password 208 * returning false means we have to go back to the user to ask for a new 209 * username password. 210 */ 211 @Override 212 public boolean isAuthorizationStale (String header) { 213 return false; /* should not be called for ntlm */ 214 } 215 216 /** 217 * Set header(s) on the given connection. 218 * @param conn The connection to apply the header(s) to 219 * @param p A source of header values for this connection, not used because 220 * HeaderParser converts the fields to lower case, use raw instead 221 * @param raw The raw header field. 222 * @return true if all goes well, false if no headers were set. 223 */ 224 @Override 225 public synchronized boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) { 226 227 try { 228 String response; 229 if (raw.length() < 6) { /* NTLM<sp> */ 230 response = buildType1Msg (); 231 } else { 232 String msg = raw.substring (5); /* skip NTLM<sp> */ 233 response = buildType3Msg (msg); 234 } 235 conn.setAuthenticationProperty(getHeaderName(), response); 236 return true; 237 } catch (IOException e) { 238 return false; 239 } catch (GeneralSecurityException e) { 240 return false; 241 } 242 } 243 244 private String buildType1Msg () { 245 byte[] msg = client.type1(); 246 String result = "NTLM " + Base64.getEncoder().encodeToString(msg); 247 return result; 248 } 249 250 private String buildType3Msg (String challenge) throws GeneralSecurityException, 251 IOException { 252 /* First decode the type2 message to get the server nonce */ 253 /* nonce is located at type2[24] for 8 bytes */ 254 255 byte[] type2 = Base64.getDecoder().decode(challenge); 256 byte[] nonce = new byte[8]; 257 new java.util.Random().nextBytes(nonce); 258 byte[] msg = client.type3(type2, nonce); 259 String result = "NTLM " + Base64.getEncoder().encodeToString(msg); 260 return result; 261 } 262} 263