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