1 /*
   2  * Copyright (c) 2005, 2015, 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     };
  82 
  83     public static boolean supportsTransparentAuth () {
  84         return false;
  85     }
  86 
  87     /**
  88      * Returns true if the given site is trusted, i.e. we can try
  89      * transparent Authentication.
  90      */
  91     public static boolean isTrustedSite(URL url) {
  92         return NTLMAuthCallback.isTrustedSite(url);
  93     }
  94 
  95     private void init0() {
  96 
  97         hostname = java.security.AccessController.doPrivileged(
  98             new java.security.PrivilegedAction<>() {
  99             public String run() {
 100                 String localhost;
 101                 try {
 102                     localhost = InetAddress.getLocalHost().getHostName();
 103                 } catch (UnknownHostException e) {
 104                      localhost = "localhost";
 105                 }
 106                 return localhost;
 107             }
 108         });
 109     };
 110 
 111     PasswordAuthentication pw;
 112 
 113     Client client;
 114     /**
 115      * Create a NTLMAuthentication:
 116      * Username may be specified as {@literal domain<BACKSLASH>username}
 117      * in the application Authenticator.
 118      * If this notation is not used, then the domain will be taken
 119      * from a system property: "http.auth.ntlm.domain".
 120      */
 121     public NTLMAuthentication(boolean isProxy, URL url, PasswordAuthentication pw) {
 122         super(isProxy ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION,
 123                 AuthScheme.NTLM,
 124                 url,
 125                 "");
 126         init (pw);
 127     }
 128 
 129     private void init (PasswordAuthentication pw) {
 130         String username;
 131         String ntdomain;
 132         char[] password;
 133         this.pw = pw;
 134         String s = pw.getUserName();
 135         int i = s.indexOf ('\\');
 136         if (i == -1) {
 137             username = s;
 138             ntdomain = defaultDomain;
 139         } else {
 140             ntdomain = s.substring (0, i).toUpperCase();
 141             username = s.substring (i+1);
 142         }
 143         password = pw.getPassword();
 144         init0();
 145         try {
 146             String version = java.security.AccessController.doPrivileged(
 147                     new sun.security.action.GetPropertyAction("ntlm.version"));
 148             client = new Client(version, hostname, username, ntdomain, password);
 149         } catch (NTLMException ne) {
 150             try {
 151                 client = new Client(null, hostname, username, ntdomain, password);
 152             } catch (NTLMException ne2) {
 153                 // Will never happen
 154                 throw new AssertionError("Really?");
 155             }
 156         }
 157     }
 158 
 159    /**
 160     * Constructor used for proxy entries
 161     */
 162     public NTLMAuthentication(boolean isProxy, String host, int port,
 163                                 PasswordAuthentication pw) {
 164         super(isProxy ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION,
 165                 AuthScheme.NTLM,
 166                 host,
 167                 port,
 168                 "");
 169         init (pw);
 170     }
 171 
 172     /**
 173      * @return true if this authentication supports preemptive authorization
 174      */
 175     @Override
 176     public boolean supportsPreemptiveAuthorization() {
 177         return false;
 178     }
 179 
 180     /**
 181      * Not supported. Must use the setHeaders() method
 182      */
 183     @Override
 184     public String getHeaderValue(URL url, String method) {
 185         throw new RuntimeException ("getHeaderValue not supported");
 186     }
 187 
 188     /**
 189      * Check if the header indicates that the current auth. parameters are stale.
 190      * If so, then replace the relevant field with the new value
 191      * and return true. Otherwise return false.
 192      * returning true means the request can be retried with the same userid/password
 193      * returning false means we have to go back to the user to ask for a new
 194      * username password.
 195      */
 196     @Override
 197     public boolean isAuthorizationStale (String header) {
 198         return false; /* should not be called for ntlm */
 199     }
 200 
 201     /**
 202      * Set header(s) on the given connection.
 203      * @param conn The connection to apply the header(s) to
 204      * @param p A source of header values for this connection, not used because
 205      *          HeaderParser converts the fields to lower case, use raw instead
 206      * @param raw The raw header field.
 207      * @return true if all goes well, false if no headers were set.
 208      */
 209     @Override
 210     public synchronized boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
 211 
 212         try {
 213             String response;
 214             if (raw.length() < 6) { /* NTLM<sp> */
 215                 response = buildType1Msg ();
 216             } else {
 217                 String msg = raw.substring (5); /* skip NTLM<sp> */
 218                 response = buildType3Msg (msg);
 219             }
 220             conn.setAuthenticationProperty(getHeaderName(), response);
 221             return true;
 222         } catch (IOException e) {
 223             return false;
 224         } catch (GeneralSecurityException e) {
 225             return false;
 226         }
 227     }
 228 
 229     private String buildType1Msg () {
 230         byte[] msg = client.type1();
 231         String result = "NTLM " + Base64.getEncoder().encodeToString(msg);
 232         return result;
 233     }
 234 
 235     private String buildType3Msg (String challenge) throws GeneralSecurityException,
 236                                                            IOException  {
 237         /* First decode the type2 message to get the server nonce */
 238         /* nonce is located at type2[24] for 8 bytes */
 239 
 240         byte[] type2 = Base64.getDecoder().decode(challenge);
 241         byte[] nonce = new byte[8];
 242         new java.util.Random().nextBytes(nonce);
 243         byte[] msg = client.type3(type2, nonce);
 244         String result = "NTLM " + Base64.getEncoder().encodeToString(msg);
 245         return result;
 246     }
 247 }
 248