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