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     };
  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             client = new Client(System.getProperty("ntlm.version"), hostname,
 147                     username, ntdomain, password);
 148         } catch (NTLMException ne) {
 149             try {
 150                 client = new Client(null, hostname, username, ntdomain, password);
 151             } catch (NTLMException ne2) {
 152                 // Will never happen
 153                 throw new AssertionError("Really?");
 154             }
 155         }
 156     }
 157 
 158    /**
 159     * Constructor used for proxy entries
 160     */
 161     public NTLMAuthentication(boolean isProxy, String host, int port,
 162                                 PasswordAuthentication pw) {
 163         super(isProxy ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION,
 164                 AuthScheme.NTLM,
 165                 host,
 166                 port,
 167                 "");
 168         init (pw);
 169     }
 170 
 171     /**
 172      * @return true if this authentication supports preemptive authorization
 173      */
 174     @Override
 175     public boolean supportsPreemptiveAuthorization() {
 176         return false;
 177     }
 178 
 179     /**
 180      * Not supported. Must use the setHeaders() method
 181      */
 182     @Override
 183     public String getHeaderValue(URL url, String method) {
 184         throw new RuntimeException ("getHeaderValue not supported");
 185     }
 186 
 187     /**
 188      * Check if the header indicates that the current auth. parameters are stale.
 189      * If so, then replace the relevant field with the new value
 190      * and return true. Otherwise return false.
 191      * returning true means the request can be retried with the same userid/password
 192      * returning false means we have to go back to the user to ask for a new
 193      * username password.
 194      */
 195     @Override
 196     public boolean isAuthorizationStale (String header) {
 197         return false; /* should not be called for ntlm */
 198     }
 199 
 200     /**
 201      * Set header(s) on the given connection.
 202      * @param conn The connection to apply the header(s) to
 203      * @param p A source of header values for this connection, not used because
 204      *          HeaderParser converts the fields to lower case, use raw instead
 205      * @param raw The raw header field.
 206      * @return true if all goes well, false if no headers were set.
 207      */
 208     @Override
 209     public synchronized boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
 210 
 211         try {
 212             String response;
 213             if (raw.length() < 6) { /* NTLM<sp> */
 214                 response = buildType1Msg ();
 215             } else {
 216                 String msg = raw.substring (5); /* skip NTLM<sp> */
 217                 response = buildType3Msg (msg);
 218             }
 219             conn.setAuthenticationProperty(getHeaderName(), response);
 220             return true;
 221         } catch (IOException e) {
 222             return false;
 223         } catch (GeneralSecurityException e) {
 224             return false;
 225         }
 226     }
 227 
 228     private String buildType1Msg () {
 229         byte[] msg = client.type1();
 230         String result = "NTLM " + Base64.getEncoder().encodeToString(msg);
 231         return result;
 232     }
 233 
 234     private String buildType3Msg (String challenge) throws GeneralSecurityException,
 235                                                            IOException  {
 236         /* First decode the type2 message to get the server nonce */
 237         /* nonce is located at type2[24] for 8 bytes */
 238 
 239         byte[] type2 = Base64.getDecoder().decode(challenge);
 240         byte[] nonce = new byte[8];
 241         new java.util.Random().nextBytes(nonce);
 242         byte[] msg = client.type3(type2, nonce);
 243         String result = "NTLM " + Base64.getEncoder().encodeToString(msg);
 244         return result;
 245     }
 246 }
 247