From 8a8455c159d220c227e7fae398c52c75ba1513f4 Mon Sep 17 00:00:00 2001 From: Darran Lofthouse Date: Tue, 4 Feb 2020 17:56:15 +0000 Subject: [PATCH] [CVE-2020-1732] Adjust JASPIC integration to create a new ServerAuthModule for each request. --- .../jaspic/DefaultAuthConfigProvider.java | 15 ++-- .../jaspic/DefaultServerAuthConfig.java | 11 +-- .../jaspic/DefaultServerAuthContext.java | 7 +- .../soteria/mechanisms/jaspic/Jaspic.java | 73 ++++++++++--------- .../servlet/SamRegistrationInstaller.java | 38 +++++----- 5 files changed, 75 insertions(+), 69 deletions(-) diff --git a/impl/src/main/java/org/glassfish/soteria/mechanisms/jaspic/DefaultAuthConfigProvider.java b/impl/src/main/java/org/glassfish/soteria/mechanisms/jaspic/DefaultAuthConfigProvider.java index bff5ed7..0007a05 100644 --- a/impl/src/main/java/org/glassfish/soteria/mechanisms/jaspic/DefaultAuthConfigProvider.java +++ b/impl/src/main/java/org/glassfish/soteria/mechanisms/jaspic/DefaultAuthConfigProvider.java @@ -17,6 +17,7 @@ package org.glassfish.soteria.mechanisms.jaspic; import java.util.Map; +import java.util.function.Supplier; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.message.AuthException; @@ -30,7 +31,7 @@ /** * This class functions as a kind of factory-factory for {@link ServerAuthConfig} instances, which are by themselves factories * for {@link ServerAuthContext} instances, which are delegates for the actual {@link ServerAuthModule} (SAM) that we're after. - * + * * @author Arjan Tijms */ public class DefaultAuthConfigProvider implements AuthConfigProvider { @@ -38,15 +39,15 @@ public class DefaultAuthConfigProvider implements AuthConfigProvider { private static final String CALLBACK_HANDLER_PROPERTY_NAME = "authconfigprovider.client.callbackhandler"; private Map providerProperties; - private ServerAuthModule serverAuthModule; + private Supplier serverAuthModuleSupplier; - public DefaultAuthConfigProvider(ServerAuthModule serverAuthModule) { - this.serverAuthModule = serverAuthModule; + public DefaultAuthConfigProvider(Supplier serverAuthModuleSupplier) { + this.serverAuthModuleSupplier = serverAuthModuleSupplier; } /** * Constructor with signature and implementation that's required by API. - * + * * @param properties provider properties * @param factory the auth config factory */ @@ -72,7 +73,7 @@ public DefaultAuthConfigProvider(Map properties, AuthConfigFacto public ServerAuthConfig getServerAuthConfig(String layer, String appContext, CallbackHandler handler) throws AuthException, SecurityException { return new DefaultServerAuthConfig(layer, appContext, handler == null ? createDefaultCallbackHandler() : handler, - providerProperties, serverAuthModule); + providerProperties, serverAuthModuleSupplier); } @Override @@ -89,7 +90,7 @@ public void refresh() { * Creates a default callback handler via the system property "authconfigprovider.client.callbackhandler", as seemingly * required by the API (API uses wording "may" create default handler). TODO: Isn't * "authconfigprovider.client.callbackhandler" JBoss specific? - * + * * @return * @throws AuthException */ diff --git a/impl/src/main/java/org/glassfish/soteria/mechanisms/jaspic/DefaultServerAuthConfig.java b/impl/src/main/java/org/glassfish/soteria/mechanisms/jaspic/DefaultServerAuthConfig.java index 8c42cf5..0db8c03 100644 --- a/impl/src/main/java/org/glassfish/soteria/mechanisms/jaspic/DefaultServerAuthConfig.java +++ b/impl/src/main/java/org/glassfish/soteria/mechanisms/jaspic/DefaultServerAuthConfig.java @@ -17,6 +17,7 @@ package org.glassfish.soteria.mechanisms.jaspic; import java.util.Map; +import java.util.function.Supplier; import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; @@ -29,7 +30,7 @@ /** * This class functions as a kind of factory for {@link ServerAuthContext} instances, which are delegates for the actual * {@link ServerAuthModule} (SAM) that we're after. - * + * * @author Arjan Tijms */ public class DefaultServerAuthConfig implements ServerAuthConfig { @@ -38,21 +39,21 @@ public class DefaultServerAuthConfig implements ServerAuthConfig { private String appContext; private CallbackHandler handler; private Map providerProperties; - private ServerAuthModule serverAuthModule; + private Supplier serverAuthModuleSupplier; public DefaultServerAuthConfig(String layer, String appContext, CallbackHandler handler, - Map providerProperties, ServerAuthModule serverAuthModule) { + Map providerProperties, Supplier serverAuthModuleSupplier) { this.layer = layer; this.appContext = appContext; this.handler = handler; this.providerProperties = providerProperties; - this.serverAuthModule = serverAuthModule; + this.serverAuthModuleSupplier = serverAuthModuleSupplier; } @Override public ServerAuthContext getAuthContext(String authContextID, Subject serviceSubject, @SuppressWarnings("rawtypes") Map properties) throws AuthException { - return new DefaultServerAuthContext(handler, serverAuthModule); + return new DefaultServerAuthContext(handler, serverAuthModuleSupplier); } // ### The methods below mostly just return what has been passed into the diff --git a/impl/src/main/java/org/glassfish/soteria/mechanisms/jaspic/DefaultServerAuthContext.java b/impl/src/main/java/org/glassfish/soteria/mechanisms/jaspic/DefaultServerAuthContext.java index 7256fc9..27f73ab 100644 --- a/impl/src/main/java/org/glassfish/soteria/mechanisms/jaspic/DefaultServerAuthContext.java +++ b/impl/src/main/java/org/glassfish/soteria/mechanisms/jaspic/DefaultServerAuthContext.java @@ -17,6 +17,7 @@ package org.glassfish.soteria.mechanisms.jaspic; import java.util.Collections; +import java.util.function.Supplier; import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; @@ -34,15 +35,15 @@ *

* Since this simple example only has a single SAM, we delegate directly to that one. Note that this {@link ServerAuthContext} * and the {@link ServerAuthModule} (SAM) share a common base interface: {@link ServerAuth}. - * + * * @author Arjan Tijms */ public class DefaultServerAuthContext implements ServerAuthContext { private final ServerAuthModule serverAuthModule; - public DefaultServerAuthContext(CallbackHandler handler, ServerAuthModule serverAuthModule) throws AuthException { - this.serverAuthModule = serverAuthModule; + public DefaultServerAuthContext(CallbackHandler handler, Supplier serverAuthModuleSupplier) throws AuthException { + this.serverAuthModule = serverAuthModuleSupplier.get(); serverAuthModule.initialize(null, null, handler, Collections. emptyMap()); } diff --git a/impl/src/main/java/org/glassfish/soteria/mechanisms/jaspic/Jaspic.java b/impl/src/main/java/org/glassfish/soteria/mechanisms/jaspic/Jaspic.java index 203f9e9..bcd67d9 100644 --- a/impl/src/main/java/org/glassfish/soteria/mechanisms/jaspic/Jaspic.java +++ b/impl/src/main/java/org/glassfish/soteria/mechanisms/jaspic/Jaspic.java @@ -25,6 +25,7 @@ import java.security.PrivilegedAction; import java.util.List; import java.util.Set; +import java.util.function.Supplier; import javax.security.enterprise.AuthenticationStatus; import javax.security.auth.Subject; @@ -43,28 +44,30 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.glassfish.soteria.cdi.spi.CDIPerRequestInitializer; + /** * A set of utility methods for using the JASPIC API - * + * * @author Arjan Tijms * */ public final class Jaspic { - + public static final String IS_AUTHENTICATION = "org.glassfish.soteria.security.message.request.authentication"; public static final String IS_AUTHENTICATION_FROM_FILTER = "org.glassfish.soteria.security.message.request.authenticationFromFilter"; public static final String IS_SECURE_RESPONSE = "org.glassfish.soteria.security.message.request.secureResponse"; public static final String IS_REFRESH = "org.glassfish.soteria.security.message.request.isRefresh"; public static final String DID_AUTHENTICATION = "org.glassfish.soteria.security.message.request.didAuthentication"; - + public static final String AUTH_PARAMS = "org.glassfish.soteria.security.message.request.authParams"; - + public static final String LOGGEDIN_USERNAME = "org.glassfish.soteria.security.message.loggedin.username"; public static final String LOGGEDIN_ROLES = "org.glassfish.soteria.security.message.loggedin.roles"; public static final String LAST_AUTH_STATUS = "org.glassfish.soteria.security.message.authStatus"; - + public static final String CONTEXT_REGISTRATION_ID = "org.glassfish.soteria.security.message.registrationId"; - + // Key in the MessageInfo Map that when present AND set to true indicated a protected resource is being accessed. // When the resource is not protected, GlassFish omits the key altogether. WebSphere does insert the key and sets // it to false. @@ -72,13 +75,13 @@ public final class Jaspic { private static final String REGISTER_SESSION = "javax.servlet.http.registerSession"; private Jaspic() {} - + public static boolean authenticate(HttpServletRequest request, HttpServletResponse response, AuthenticationParameters authParameters) { try { // JASPIC 1.1 does not have any way to distinguish between a // SAM called at start of a request or following request#authenticate. // See https://java.net/jira/browse/JASPIC_SPEC-5 - + // We now add this as a request attribute instead, but should better // be the MessageInfo map request.setAttribute(IS_AUTHENTICATION, true); @@ -101,10 +104,10 @@ public static AuthenticationParameters getAuthParameters(HttpServletRequest requ if (authParameters == null) { authParameters = new AuthenticationParameters(); } - + return authParameters; } - + public static void logout(HttpServletRequest request, HttpServletResponse response) { try { request.logout(); @@ -131,21 +134,21 @@ public Void run() { public static boolean isRegisterSession(MessageInfo messageInfo) { return Boolean.valueOf((String)messageInfo.getMap().get(REGISTER_SESSION)); } - + public static boolean isProtectedResource(MessageInfo messageInfo) { return Boolean.valueOf((String) messageInfo.getMap().get(IS_MANDATORY)); } - + @SuppressWarnings("unchecked") public static void setRegisterSession(MessageInfo messageInfo, String username, Set roles) { messageInfo.getMap().put("javax.servlet.http.registerSession", TRUE.toString()); - + HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage(); request.setAttribute(LOGGEDIN_USERNAME, username); // TODO: check for existing roles and add request.setAttribute(LOGGEDIN_ROLES, roles); } - + public static boolean isAuthenticationRequest(HttpServletRequest request) { return TRUE.equals(request.getAttribute(IS_AUTHENTICATION)); } @@ -202,70 +205,70 @@ public static AuthStatus fromAuthenticationStatus(AuthenticationStatus authentic throw new IllegalStateException("Unhandled status:" + authenticationStatus.name()); } } - + /** * Should be called when the callback handler is used with the intention that an actual * user is going to be authenticated (as opposed to using the handler for the "do nothing" protocol * which uses the unauthenticated identity). - * + * * @param request The involved HTTP servlet request. - * + * */ public static void setDidAuthentication(HttpServletRequest request) { request.setAttribute(DID_AUTHENTICATION, TRUE); } - + /** * Gets the app context ID from the servlet context. - * + * *

* The app context ID is the ID that JASPIC associates with the given application. * In this case that given application is the web application corresponding to the * ServletContext. - * + * * @param context the servlet context for which to obtain the JASPIC app context ID * @return the app context ID for the web application corresponding to the given context */ public static String getAppContextID(ServletContext context) { return context.getVirtualServerName() + " " + context.getContextPath(); } - + /** * Registers a server auth module as the one and only module for the application corresponding to * the given servlet context. - * + * *

* This will override any other modules that have already been registered, either via proprietary * means or using the standard API. - * - * @param serverAuthModule the server auth module to be registered + * + * @param cdiPerRequestInitializer A {@link CDIPerRequestInitializer} to pass to the {@link ServerAuthModule} * @param servletContext the context of the app for which the module is registered * @return A String identifier assigned by an underlying factory corresponding to an underlying factory-factory-factory registration */ - public static String registerServerAuthModule(ServerAuthModule serverAuthModule, ServletContext servletContext) { - + public static String registerServerAuthModule(CDIPerRequestInitializer cdiPerRequestInitializer, ServletContext servletContext) { + // Register the factory-factory-factory for the SAM String registrationId = AccessController.doPrivileged(new PrivilegedAction() { public String run() { return AuthConfigFactory.getFactory().registerConfigProvider( - new DefaultAuthConfigProvider(serverAuthModule), - "HttpServlet", - getAppContextID(servletContext), + new DefaultAuthConfigProvider(() -> new HttpBridgeServerAuthModule(cdiPerRequestInitializer)), + "HttpServlet", + getAppContextID(servletContext), "Default single SAM authentication config provider"); } }); - + // Remember the registration ID returned by the factory, so we can unregister the JASPIC module when the web module // is undeployed. JASPIC being the low level API that it is won't do this automatically. servletContext.setAttribute(CONTEXT_REGISTRATION_ID, registrationId); - + return registrationId; } - + /** * Deregisters the server auth module (and encompassing wrappers/factories) that was previously registered via a call * to registerServerAuthModule. - * + * * @param servletContext the context of the app for which the module is deregistered */ public static void deregisterServerAuthModule(ServletContext servletContext) { @@ -278,6 +281,6 @@ public Boolean run() { }); } } - - + + } diff --git a/impl/src/main/java/org/glassfish/soteria/servlet/SamRegistrationInstaller.java b/impl/src/main/java/org/glassfish/soteria/servlet/SamRegistrationInstaller.java index 90fe145..2bc03bc 100644 --- a/impl/src/main/java/org/glassfish/soteria/servlet/SamRegistrationInstaller.java +++ b/impl/src/main/java/org/glassfish/soteria/servlet/SamRegistrationInstaller.java @@ -39,19 +39,19 @@ import org.glassfish.soteria.mechanisms.jaspic.Jaspic; /** - * If an HttpAuthenticationMechanism implementation has been found on the classpath, this + * If an HttpAuthenticationMechanism implementation has been found on the classpath, this * initializer installs a bridge SAM that delegates the validateRequest, secureResponse and * cleanSubject methods from the SAM to the HttpAuthenticationMechanism. - * + * *

* The bridge SAM uses CDI.current() to obtain the HttpAuthenticationMechanism, therefore * fully enabling CDI in the implementation of that interface. - * + * * @author Arjan Tijms * */ public class SamRegistrationInstaller implements ServletContainerInitializer, ServletContextListener { - + private static final Logger logger = Logger.getLogger(SamRegistrationInstaller.class.getName()); @Override @@ -59,25 +59,25 @@ public void onStartup(Set> c, ServletContext ctx) throws ServletExcepti // Obtain a reference to the CdiExtension that was used to see if // there's an enabled bean - + CDI cdi; try { cdi = CDI.current(); - + if (logger.isLoggable(INFO)) { String version = getClass().getPackage().getImplementationVersion(); logger.log(INFO, "Initializing Soteria {0} for context ''{1}''", new Object[]{version, ctx.getContextPath()}); } - + } catch (IllegalStateException e) { - // On GlassFish 4.1.1/Payara 4.1.1.161 CDI is not initialized (org.jboss.weld.Container#initialize is not called), + // On GlassFish 4.1.1/Payara 4.1.1.161 CDI is not initialized (org.jboss.weld.Container#initialize is not called), // and calling CDI.current() will throw an exception. It's no use to continue then. // TODO: Do we need to find out *why* the default module does not have CDI initialized? logger.log(FINEST, "CDI not available for app context id: " + Jaspic.getAppContextID(ctx), e); - + return; } - + CdiExtension cdiExtension = cdi.select(CdiExtension.class).get(); if (cdiExtension.isHttpAuthenticationMechanismFound()) { @@ -85,34 +85,34 @@ public void onStartup(Set> c, ServletContext ctx) throws ServletExcepti // A SAM must be registered at this point, since the programmatically added // Listener is for some reason restricted (not allow) from calling // getVirtualServerName. At this point we're still allowed to call this. - + // TODO: Ask the Servlet EG to address this? Is there any ground for this restriction??? - + CDIPerRequestInitializer cdiPerRequestInitializer = null; - + if (!isEmpty(System.getProperty("wlp.server.name"))) { // Hardcode server check for now. TODO: design/implement proper service loader/SPI for this cdiPerRequestInitializer = new LibertyCDIPerRequestInitializer(); logger.log(INFO, "Running on Liberty - installing CDI request scope activator"); } - - registerServerAuthModule(new HttpBridgeServerAuthModule(cdiPerRequestInitializer), ctx); - + + registerServerAuthModule(cdiPerRequestInitializer, ctx); + // Add a listener so we can process the context destroyed event, which is needed // to de-register the SAM correctly. ctx.addListener(this); } } - + @Override public void contextInitialized(ServletContextEvent sce) { // noop } - + @Override public void contextDestroyed(ServletContextEvent sce) { deregisterServerAuthModule(sce.getServletContext()); } - + }