ttomcat-1778514358873.zip-extract/apache-tomcat-11.0.18-src/java/org/apache/catalina/authenticator/SpnegoAuthenticator.java

Path
ttomcat-1778514358873.zip-extract/apache-tomcat-11.0.18-src/java/org/apache/catalina/authenticator/SpnegoAuthenticator.java
Status
scanned
Type
file
Name
SpnegoAuthenticator.java
Extension
.java
Programming language
Java
Mime type
text/plain
File type
ASCII text, with CRLF line terminators
Tag

      
    
Rootfs path

      
    
Size
17849 (17.4 KB)
MD5
3a88bda5f25da9ca6add5a6155b9ecfc
SHA1
f162900c327e4d48d518c4f35e646bf8461b702e
SHA256
b1b91f376d4aa41d774ac89c13a4be4186eeb005978b4bcae1668900ea4a1c07
SHA512

      
    
SHA1_git
d5d0ceab74a786494da5ec2163c2fe562a1e3362
Is binary

      
    
Is text
True
Is archive

      
    
Is media

      
    
Is legal

      
    
Is manifest

      
    
Is readme

      
    
Is top level

      
    
Is key file

      
    
SpnegoAuthenticator.java | 17.4 KB |

/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.catalina.authenticator; import java.io.File; import java.io.IOException; import java.security.Principal; import java.util.Base64; import java.util.LinkedHashMap; import java.util.concurrent.CompletionException; import java.util.regex.Pattern; import javax.security.auth.Subject; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import jakarta.servlet.http.HttpServletResponse; import org.apache.catalina.LifecycleException; import org.apache.catalina.connector.Request; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.buf.ByteChunk; import org.apache.tomcat.util.buf.MessageBytes; import org.apache.tomcat.util.compat.JreCompat; import org.apache.tomcat.util.compat.JreVendor; import org.ietf.jgss.GSSContext; import org.ietf.jgss.GSSCredential; import org.ietf.jgss.GSSException; import org.ietf.jgss.GSSManager; import org.ietf.jgss.Oid; /** * A SPNEGO authenticator that uses the SPNEGO/Kerberos support built in to Java 6. Successful Kerberos authentication * depends on the correct configuration of multiple components. If the configuration is invalid, the error messages are * often cryptic although a Google search will usually point you in the right direction. */ public class SpnegoAuthenticator extends AuthenticatorBase { private final Log log = LogFactory.getLog(SpnegoAuthenticator.class); // must not be static private static final String AUTH_HEADER_VALUE_NEGOTIATE = "Negotiate"; private String loginConfigName = Constants.DEFAULT_LOGIN_MODULE_NAME; public String getLoginConfigName() { return loginConfigName; } public void setLoginConfigName(String loginConfigName) { this.loginConfigName = loginConfigName; } private boolean storeDelegatedCredential = true; public boolean isStoreDelegatedCredential() { return storeDelegatedCredential; } public void setStoreDelegatedCredential(boolean storeDelegatedCredential) { this.storeDelegatedCredential = storeDelegatedCredential; } private Pattern noKeepAliveUserAgents = null; public String getNoKeepAliveUserAgents() { Pattern p = noKeepAliveUserAgents; if (p == null) { return null; } else { return p.pattern(); } } public void setNoKeepAliveUserAgents(String noKeepAliveUserAgents) { if (noKeepAliveUserAgents == null || noKeepAliveUserAgents.isEmpty()) { this.noKeepAliveUserAgents = null; } else { this.noKeepAliveUserAgents = Pattern.compile(noKeepAliveUserAgents); } } private boolean applyJava8u40Fix = true; public boolean getApplyJava8u40Fix() { return applyJava8u40Fix; } public void setApplyJava8u40Fix(boolean applyJava8u40Fix) { this.applyJava8u40Fix = applyJava8u40Fix; } @Override protected String getAuthMethod() { return Constants.SPNEGO_METHOD; } @Override protected void initInternal() throws LifecycleException { super.initInternal(); // Kerberos configuration file location String krb5Conf = System.getProperty(Constants.KRB5_CONF_PROPERTY); if (krb5Conf == null) { // System property not set, use the Tomcat default File krb5ConfFile = new File(container.getCatalinaBase(), Constants.DEFAULT_KRB5_CONF); System.setProperty(Constants.KRB5_CONF_PROPERTY, krb5ConfFile.getAbsolutePath()); } // JAAS configuration file location String jaasConf = System.getProperty(Constants.JAAS_CONF_PROPERTY); if (jaasConf == null) { // System property not set, use the Tomcat default File jaasConfFile = new File(container.getCatalinaBase(), Constants.DEFAULT_JAAS_CONF); System.setProperty(Constants.JAAS_CONF_PROPERTY, jaasConfFile.getAbsolutePath()); } } @Override protected boolean doAuthenticate(Request request, HttpServletResponse response) throws IOException { /* * Reauthentication using the cached user name and password (if any) is not enabled for SPNEGO authentication. * This is because the delegated credentials will nto be available unless a normal SPNEGO authentication takes * place. * * Reauthentication was introduced to handle the case where the Realm took additional actions on authentication. * Reauthenticating with the cached user name and password may not be sufficient for SPNEGO since it will not * make the delegated credentials available that a web application may depend on. Therefore, the * reauthentication behaviour for SPNEGO is to perform a normal SPNEGO authentication. */ if (checkForCachedAuthentication(request, response, false)) { return true; } MessageBytes authorization = request.getCoyoteRequest().getMimeHeaders().getValue("authorization"); if (authorization == null) { if (log.isDebugEnabled()) { log.debug(sm.getString("authenticator.noAuthHeader")); } response.setHeader(AUTH_HEADER_NAME, AUTH_HEADER_VALUE_NEGOTIATE); response.sendError(HttpServletResponse.SC_UNAUTHORIZED); return false; } authorization.toBytes(); ByteChunk authorizationBC = authorization.getByteChunk(); if (!authorizationBC.startsWithIgnoreCase("negotiate ", 0)) { if (log.isDebugEnabled()) { log.debug(sm.getString("spnegoAuthenticator.authHeaderNotNego")); } response.setHeader(AUTH_HEADER_NAME, AUTH_HEADER_VALUE_NEGOTIATE); response.sendError(HttpServletResponse.SC_UNAUTHORIZED); return false; } authorizationBC.setStart(authorizationBC.getStart() + 10); byte[] encoded = new byte[authorizationBC.getLength()]; System.arraycopy(authorizationBC.getBuffer(), authorizationBC.getStart(), encoded, 0, authorizationBC.getLength()); byte[] decoded = Base64.getDecoder().decode(encoded); if (getApplyJava8u40Fix()) { SpnegoTokenFixer.fix(decoded); } if (decoded.length == 0) { if (log.isDebugEnabled()) { log.debug(sm.getString("spnegoAuthenticator.authHeaderNoToken")); } response.setHeader(AUTH_HEADER_NAME, AUTH_HEADER_VALUE_NEGOTIATE); response.sendError(HttpServletResponse.SC_UNAUTHORIZED); return false; } LoginContext lc = null; GSSContext gssContext = null; byte[] outToken; Principal principal; try { try { lc = new LoginContext(getLoginConfigName()); lc.login(); } catch (LoginException e) { log.error(sm.getString("spnegoAuthenticator.serviceLoginFail"), e); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return false; } Subject subject = lc.getSubject(); // Assume the GSSContext is stateless // TODO: Confirm this assumption final GSSManager manager = GSSManager.getInstance(); // IBM JDK only understands indefinite lifetime final int credentialLifetime; if (JreVendor.IS_IBM_JVM) { credentialLifetime = GSSCredential.INDEFINITE_LIFETIME; } else { credentialLifetime = GSSCredential.DEFAULT_LIFETIME; } gssContext = manager.createContext(JreCompat.getInstance().callAs(subject, () -> { return manager.createCredential(null, credentialLifetime, new Oid("1.3.6.1.5.5.2"), GSSCredential.ACCEPT_ONLY); })); final GSSContext gssContextFinal = gssContext; outToken = JreCompat.getInstance().callAs(subject, () -> { return gssContextFinal.acceptSecContext(decoded, 0, decoded.length); }); if (outToken == null) { if (log.isDebugEnabled()) { log.debug(sm.getString("spnegoAuthenticator.ticketValidateFail")); } // Start again response.setHeader(AUTH_HEADER_NAME, AUTH_HEADER_VALUE_NEGOTIATE); response.sendError(HttpServletResponse.SC_UNAUTHORIZED); return false; } principal = JreCompat.getInstance().callAs(subject, () -> { return context.getRealm().authenticate(gssContextFinal, storeDelegatedCredential); }); } catch (GSSException e) { if (log.isDebugEnabled()) { log.debug(sm.getString("spnegoAuthenticator.ticketValidateFail"), e); } response.setHeader(AUTH_HEADER_NAME, AUTH_HEADER_VALUE_NEGOTIATE); response.sendError(HttpServletResponse.SC_UNAUTHORIZED); return false; } catch (CompletionException e) { Throwable cause = e.getCause(); if (cause instanceof GSSException) { if (log.isDebugEnabled()) { log.debug(sm.getString("spnegoAuthenticator.serviceLoginFail"), e); } } else { log.error(sm.getString("spnegoAuthenticator.serviceLoginFail"), e); } response.setHeader(AUTH_HEADER_NAME, AUTH_HEADER_VALUE_NEGOTIATE); response.sendError(HttpServletResponse.SC_UNAUTHORIZED); return false; } finally { if (gssContext != null) { try { gssContext.dispose(); } catch (GSSException e) { // Ignore } } if (lc != null) { try { lc.logout(); } catch (LoginException e) { // Ignore } } } // Send response token on success and failure response.setHeader(AUTH_HEADER_NAME, AUTH_HEADER_VALUE_NEGOTIATE + " " + Base64.getEncoder().encodeToString(outToken)); if (principal != null) { register(request, response, principal, Constants.SPNEGO_METHOD, principal.getName(), null); Pattern p = noKeepAliveUserAgents; if (p != null) { MessageBytes ua = request.getCoyoteRequest().getMimeHeaders().getValue("user-agent"); if (ua != null && p.matcher(ua.toString()).matches()) { response.setHeader("Connection", "close"); } } return true; } response.sendError(HttpServletResponse.SC_UNAUTHORIZED); return false; } @Override protected boolean isPreemptiveAuthPossible(Request request) { MessageBytes authorizationHeader = request.getCoyoteRequest().getMimeHeaders().getValue("authorization"); return authorizationHeader != null && authorizationHeader.startsWithIgnoreCase("negotiate ", 0); } /** * This class implements a hack around an incompatibility between the SPNEGO implementation in Windows and the * SPNEGO implementation in Java 8 update 40 onwards. It was introduced by the change to fix this bug: * <a href="https://bugs.openjdk.java.net/browse/JDK-8048194">JDK-8048194</a> (note: the change applied is not the * one suggested in the bug report) * <p> * It is not clear to me if Windows, Java or Tomcat is at fault here. I think it is Java, but I could be wrong. * <p> * This hack works by re-ordering the list of mechTypes in the NegTokenInit token. */ public static class SpnegoTokenFixer { public static void fix(byte[] token) { SpnegoTokenFixer fixer = new SpnegoTokenFixer(token); fixer.fix(); } private final byte[] token; private int pos = 0; private SpnegoTokenFixer(byte[] token) { this.token = token; } // Fixes the token in-place private void fix() { /* * Useful references: http://tools.ietf.org/html/rfc4121#page-5 http://tools.ietf.org/html/rfc2743#page-81 * https://msdn.microsoft.com/en-us/library/ms995330.aspx */ // Scan until we find the mech types list. If we find anything // unexpected, abort the fix process. if (!tag(0x60)) { return; } if (!length()) { return; } if (!oid("1.3.6.1.5.5.2")) { return; } if (!tag(0xa0)) { return; } if (!length()) { return; } if (!tag(0x30)) { return; } if (!length()) { return; } if (!tag(0xa0)) { return; } lengthAsInt(); if (!tag(0x30)) { return; } // Now at the start of the mechType list. // Read the mechTypes into an ordered set int mechTypesLen = lengthAsInt(); int mechTypesStart = pos; LinkedHashMap<String,int[]> mechTypeEntries = new LinkedHashMap<>(); while (pos < mechTypesStart + mechTypesLen) { int[] value = new int[2]; value[0] = pos; String key = oidAsString(); value[1] = pos - value[0]; mechTypeEntries.put(key, value); } // Now construct the re-ordered mechType list byte[] replacement = new byte[mechTypesLen]; int replacementPos = 0; int[] first = mechTypeEntries.remove("1.2.840.113554.1.2.2"); if (first != null) { System.arraycopy(token, first[0], replacement, replacementPos, first[1]); replacementPos += first[1]; } for (int[] markers : mechTypeEntries.values()) { System.arraycopy(token, markers[0], replacement, replacementPos, markers[1]); replacementPos += markers[1]; } // Finally, replace the original mechType list with the re-ordered // one. System.arraycopy(replacement, 0, token, mechTypesStart, mechTypesLen); } private boolean tag(int expected) { return (token[pos++] & 0xFF) == expected; } private boolean length() { // No need to retain the length - just need to consume it and make // sure it is valid. int len = lengthAsInt(); return pos + len == token.length; } private int lengthAsInt() { int len = token[pos++] & 0xFF; if (len > 127) { int bytes = len - 128; len = 0; for (int i = 0; i < bytes; i++) { len = len << 8; len = len + (token[pos++] & 0xff); } } return len; } private boolean oid(String expected) { return expected.equals(oidAsString()); } private String oidAsString() { if (!tag(0x06)) { return null; } StringBuilder result = new StringBuilder(); int len = lengthAsInt(); // First byte is special case int v = token[pos++] & 0xFF; int c2 = v % 40; int c1 = (v - c2) / 40; result.append(c1); result.append('.'); result.append(c2); int c = 0; boolean write = false; for (int i = 1; i < len; i++) { int b = token[pos++] & 0xFF; if (b > 127) { b -= 128; } else { write = true; } c = c << 7; c += b; if (write) { result.append('.'); result.append(c); c = 0; write = false; } } return result.toString(); } } }
Detected license expression
apache-2.0
Detected license expression (SPDX)
Apache-2.0
Percentage of license text
7.45
Copyrights

      
    
Holders

      
    
Authors

      
    
License detections License expression License expression SPDX
apache_2_0-4bde3f57-78aa-4201-96bf-531cba09e7de apache-2.0 Apache-2.0
URL Start line End line
http://www.apache.org/licenses/LICENSE-2.0 9 9
https://bugs.openjdk.java.net/browse/JDK-8048194 312 312
http://tools.ietf.org/html/rfc4121#page-5 339 339
http://tools.ietf.org/html/rfc2743#page-81 339 339
https://msdn.microsoft.com/en-us/library/ms995330.aspx 340 340