1 /*
   2  * Copyright (c) 2005, 2016, 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;
  27 
  28 import java.net.URL;
  29 import java.io.IOException;
  30 import java.net.Authenticator.RequestorType;
  31 import java.util.Base64;
  32 import java.util.HashMap;
  33 import sun.net.www.HeaderParser;
  34 import sun.util.logging.PlatformLogger;
  35 import static sun.net.www.protocol.http.AuthScheme.NEGOTIATE;
  36 import static sun.net.www.protocol.http.AuthScheme.KERBEROS;
  37 import sun.security.action.GetPropertyAction;
  38 
  39 /**
  40  * NegotiateAuthentication:
  41  *
  42  * @author weijun.wang@sun.com
  43  * @since 1.6
  44  */
  45 
  46 class NegotiateAuthentication extends AuthenticationInfo {
  47 
  48     @java.io.Serial
  49     private static final long serialVersionUID = 100L;
  50     private static final PlatformLogger logger = HttpURLConnection.getHttpLogger();
  51 
  52     private final HttpCallerInfo hci;
  53 
  54     // These maps are used to manage the GSS availability for diffrent
  55     // hosts. The key for both maps is the host name.
  56     // <code>supported</code> is set when isSupported is checked,
  57     // if it's true, a cached Negotiator is put into <code>cache</code>.
  58     // the cache can be used only once, so after the first use, it's cleaned.
  59     static HashMap <String, Boolean> supported = null;
  60     static ThreadLocal <HashMap <String, Negotiator>> cache = null;
  61     /* Whether cache is enabled for Negotiate/Kerberos */
  62     private static final boolean cacheSPNEGO;
  63     static {
  64         String spnegoCacheProp =
  65             GetPropertyAction.privilegedGetProperty("jdk.spnego.cache", "true");
  66         cacheSPNEGO = Boolean.parseBoolean(spnegoCacheProp);
  67     }
  68 
  69     // The HTTP Negotiate Helper
  70     private Negotiator negotiator = null;
  71 
  72    /**
  73     * Constructor used for both WWW and proxy entries.
  74     * @param hci a schemed object.
  75     */
  76     public NegotiateAuthentication(HttpCallerInfo hci) {
  77         super(RequestorType.PROXY==hci.authType ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION,
  78               hci.scheme.equalsIgnoreCase("Negotiate") ? NEGOTIATE : KERBEROS,
  79               hci.url,
  80               "",
  81               AuthenticatorKeys.getKey(hci.authenticator));
  82         this.hci = hci;
  83     }
  84 
  85     /**
  86      * @return true if this authentication supports preemptive authorization
  87      */
  88     @Override
  89     public boolean supportsPreemptiveAuthorization() {
  90         return false;
  91     }
  92 
  93     /**
  94      * Find out if the HttpCallerInfo supports Negotiate protocol.
  95      * @return true if supported
  96      */
  97     public static boolean isSupported(HttpCallerInfo hci) {
  98         ClassLoader loader = null;
  99         try {
 100             loader = Thread.currentThread().getContextClassLoader();
 101         } catch (SecurityException se) {
 102             if (logger.isLoggable(PlatformLogger.Level.FINER)) {
 103                 logger.finer("NegotiateAuthentication: " +
 104                     "Attempt to get the context class loader failed - " + se);
 105             }
 106         }
 107 
 108         if (loader != null) {
 109             // Lock on the class loader instance to avoid the deadlock engaging
 110             // the lock in "ClassLoader.loadClass(String, boolean)" method.
 111             synchronized (loader) {
 112                 return isSupportedImpl(hci);
 113             }
 114         }
 115         return isSupportedImpl(hci);
 116     }
 117 
 118     /**
 119      * Find out if the HttpCallerInfo supports Negotiate protocol. In order to
 120      * find out yes or no, an initialization of a Negotiator object against it
 121      * is tried. The generated object will be cached under the name of ths
 122      * hostname at a success try.<br>
 123      *
 124      * If this method is called for the second time on an HttpCallerInfo with
 125      * the same hostname, the answer is retrieved from cache.
 126      *
 127      * @return true if supported
 128      */
 129     private static synchronized boolean isSupportedImpl(HttpCallerInfo hci) {
 130         if (supported == null) {
 131             supported = new HashMap<>();
 132         }
 133         String hostname = hci.host;
 134         hostname = hostname.toLowerCase();
 135         if (supported.containsKey(hostname)) {
 136             return supported.get(hostname);
 137         }
 138 
 139         Negotiator neg = Negotiator.getNegotiator(hci);
 140         if (neg != null) {
 141             supported.put(hostname, true);
 142             // the only place cache.put is called. here we can make sure
 143             // the object is valid and the oneToken inside is not null
 144             if (cache == null) {
 145                 cache = new ThreadLocal<>() {
 146                     @Override
 147                     protected HashMap<String, Negotiator> initialValue() {
 148                         return new HashMap<>();
 149                     }
 150                 };
 151             }
 152             cache.get().put(hostname, neg);
 153             return true;
 154         } else {
 155             supported.put(hostname, false);
 156             return false;
 157         }
 158     }
 159 
 160     private static synchronized HashMap<String, Negotiator> getCache() {
 161         if (cache == null) return null;
 162         return cache.get();
 163     }
 164 
 165     @Override
 166     protected boolean useAuthCache() {
 167         return super.useAuthCache() && cacheSPNEGO;
 168     }
 169 
 170     /**
 171      * Not supported. Must use the setHeaders() method
 172      */
 173     @Override
 174     public String getHeaderValue(URL url, String method) {
 175         throw new RuntimeException ("getHeaderValue not supported");
 176     }
 177 
 178     /**
 179      * Check if the header indicates that the current auth. parameters are stale.
 180      * If so, then replace the relevant field with the new value
 181      * and return true. Otherwise return false.
 182      * returning true means the request can be retried with the same userid/password
 183      * returning false means we have to go back to the user to ask for a new
 184      * username password.
 185      */
 186     @Override
 187     public boolean isAuthorizationStale (String header) {
 188         return false; /* should not be called for Negotiate */
 189     }
 190 
 191     /**
 192      * Set header(s) on the given connection.
 193      * @param conn The connection to apply the header(s) to
 194      * @param p A source of header values for this connection, not used because
 195      *          HeaderParser converts the fields to lower case, use raw instead
 196      * @param raw The raw header field.
 197      * @return true if all goes well, false if no headers were set.
 198      */
 199     @Override
 200     public synchronized boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
 201 
 202         try {
 203             String response;
 204             byte[] incoming = null;
 205             String[] parts = raw.split("\\s+");
 206             if (parts.length > 1) {
 207                 incoming = Base64.getDecoder().decode(parts[1]);
 208             }
 209             response = hci.scheme + " " + Base64.getEncoder().encodeToString(
 210                         incoming==null?firstToken():nextToken(incoming));
 211 
 212             conn.setAuthenticationProperty(getHeaderName(), response);
 213             return true;
 214         } catch (IOException e) {
 215             return false;
 216         }
 217     }
 218 
 219     /**
 220      * return the first token.
 221      * @return the token
 222      * @throws IOException if <code>Negotiator.getNegotiator()</code> or
 223      *                     <code>Negotiator.firstToken()</code> failed.
 224      */
 225     private byte[] firstToken() throws IOException {
 226         negotiator = null;
 227         HashMap <String, Negotiator> cachedMap = getCache();
 228         if (cachedMap != null) {
 229             negotiator = cachedMap.get(getHost());
 230             if (negotiator != null) {
 231                 cachedMap.remove(getHost()); // so that it is only used once
 232             }
 233         }
 234         if (negotiator == null) {
 235             negotiator = Negotiator.getNegotiator(hci);
 236             if (negotiator == null) {
 237                 IOException ioe = new IOException("Cannot initialize Negotiator");
 238                 throw ioe;
 239             }
 240         }
 241 
 242         return negotiator.firstToken();
 243     }
 244 
 245     /**
 246      * return more tokens
 247      * @param token the token to be fed into <code>negotiator.nextToken()</code>
 248      * @return the token
 249      * @throws IOException if <code>negotiator.nextToken()</code> throws Exception.
 250      *  May happen if the input token is invalid.
 251      */
 252     private byte[] nextToken(byte[] token) throws IOException {
 253         return negotiator.nextToken(token);
 254     }
 255 
 256     // MS will send a final WWW-Authenticate even if the status is already
 257     // 200 OK. The token can be fed into initSecContext() again to determine
 258     // if the server can be trusted. This is not the same concept as Digest's
 259     // Authentication-Info header.
 260     //
 261     // Currently we ignore this header.
 262 
 263 }