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