From 12fdf47c270becc3bdd8e158a070012cbd28806d Mon Sep 17 00:00:00 2001 From: Karel Maxa Date: Fri, 16 Aug 2024 15:50:08 +0200 Subject: [PATCH 1/2] Ignore non-negotiate auth requests in IWA module (#55) --- .../jaspi/modules/iwa/IWAModule.java | 120 +++++++++--------- .../jaspi/modules/iwa/wdsso/WDSSO.java | 60 ++------- 2 files changed, 68 insertions(+), 112 deletions(-) diff --git a/auth-filters/forgerock-authn-filter/forgerock-jaspi-modules/forgerock-jaspi-iwa-module/src/main/java/org/forgerock/jaspi/modules/iwa/IWAModule.java b/auth-filters/forgerock-authn-filter/forgerock-jaspi-modules/forgerock-jaspi-iwa-module/src/main/java/org/forgerock/jaspi/modules/iwa/IWAModule.java index 125312e96..4fd18a218 100644 --- a/auth-filters/forgerock-authn-filter/forgerock-jaspi-modules/forgerock-jaspi-iwa-module/src/main/java/org/forgerock/jaspi/modules/iwa/IWAModule.java +++ b/auth-filters/forgerock-authn-filter/forgerock-jaspi-modules/forgerock-jaspi-iwa-module/src/main/java/org/forgerock/jaspi/modules/iwa/IWAModule.java @@ -12,6 +12,7 @@ * information: "Portions copyright [year] [name of copyright owner]". * * Copyright 2013-2016 ForgeRock AS. + * Portions Copyright 2024 Wren Security */ package org.forgerock.jaspi.modules.iwa; @@ -23,13 +24,11 @@ import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.message.AuthException; import javax.security.auth.message.AuthStatus; import javax.security.auth.message.MessagePolicy; import java.security.Principal; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; import java.util.Map; import org.forgerock.caf.authentication.api.AsyncServerAuthModule; @@ -38,6 +37,7 @@ import org.forgerock.http.protocol.Request; import org.forgerock.http.protocol.Response; import org.forgerock.http.protocol.Status; +import org.forgerock.jaspi.modules.iwa.wdsso.Base64; import org.forgerock.jaspi.modules.iwa.wdsso.WDSSO; import org.forgerock.util.promise.Promise; @@ -49,106 +49,100 @@ public class IWAModule implements AsyncServerAuthModule { private static final String IWA_FAILED = "iwa-failed"; + private static final String NEGOTIATE_AUTH_SCHEME = "Negotiate"; - private CallbackHandler handler; - private Map options; + private Map options; @Override public String getModuleId() { return "IWA"; } - /** - * {@inheritDoc} - */ @Override public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, CallbackHandler handler, Map options) throws AuthenticationException { - this.handler = handler; this.options = options; } - /** - * {@inheritDoc} - */ @Override public Collection> getSupportedMessageTypes() { - return Arrays.asList(new Class[]{Request.class, Response.class}); + return Arrays.asList(new Class[]{ Request.class, Response.class }); } /** * Validates the request by checking the Authorization header in the request for a IWA token and processes that * for authentication. - * - * @param messageInfo {@inheritDoc} - * @param clientSubject {@inheritDoc} - * @param serviceSubject {@inheritDoc} - * @return {@inheritDoc} */ @Override public Promise validateRequest(MessageInfoContext messageInfo, Subject clientSubject, Subject serviceSubject) { - - LOG.debug("IWAModule: validateRequest START"); - Request request = messageInfo.getRequest(); Response response = messageInfo.getResponse(); - String httpAuthorization = request.getHeaders().getFirst("Authorization"); + String authorizationHeader = request.getHeaders().getFirst("Authorization"); + + if (authorizationHeader == null || authorizationHeader.isEmpty()) { + LOG.debug("IWAModule: Authorization Header NOT set in request."); + + response.getHeaders().put("WWW-Authenticate", NEGOTIATE_AUTH_SCHEME); + response.setStatus(Status.UNAUTHORIZED); + response.setEntity(Map.of("failure", true, "recason", IWA_FAILED)); + return newResultPromise(SEND_CONTINUE); + } + + // Handle only negotiate authentication requests + if (!authorizationHeader.startsWith(NEGOTIATE_AUTH_SCHEME)) { + return newResultPromise(SEND_FAILURE); + } + + LOG.debug("IWAModule: Negotiate authorization header set in request."); + + // Check SPNEGO token + byte[] spnegoToken = extractSpnegoToken(authorizationHeader); + if (spnegoToken == null) { + return newExceptionPromise(new AuthenticationException("Invalid SPNEGO token")); + } + // Perform Kerberos authentication try { - if (httpAuthorization == null || "".equals(httpAuthorization)) { - LOG.debug("IWAModule: Authorization Header NOT set in request."); - - response.getHeaders().put("WWW-Authenticate", "Negotiate"); - response.setStatus(Status.UNAUTHORIZED); - Map entity = new HashMap<>(); - entity.put("failure", true); - entity.put("recason", IWA_FAILED); - response.setEntity(entity); - - return newResultPromise(SEND_CONTINUE); - } else { - LOG.debug("IWAModule: Authorization Header set in request."); - try { - final String username = new WDSSO().process(options, messageInfo, request); - LOG.debug("IWAModule: IWA successful with username, {}", username); - - clientSubject.getPrincipals().add(new Principal() { - public String getName() { - return username; - } - }); - } catch (Exception e) { - LOG.debug("IWAModule: IWA has failed. {}", e.getMessage()); - return newExceptionPromise(new AuthenticationException("IWA has failed")); - } + final String username = new WDSSO().process(options, messageInfo, spnegoToken); + LOG.debug("IWAModule: IWA successful with username, {}", username); - return newResultPromise(SUCCESS); - } - } finally { - LOG.debug("IWAModule: validateRequest END"); + clientSubject.getPrincipals().add(new Principal() { + @Override + public String getName() { + return username; + } + }); + return newResultPromise(SUCCESS); + } catch (Exception e) { + LOG.debug("IWAModule: IWA has failed. {}", e.getMessage()); + return newExceptionPromise(new AuthenticationException("IWA has failed")); } } - /** - * Always returns AuthStatus.SEND_SUCCESS. - * - * @param messageInfo {@inheritDoc} - * @param serviceSubject {@inheritDoc} - * @return {@inheritDoc} - */ @Override - public Promise secureResponse(MessageInfoContext messageInfo, - Subject serviceSubject) { + public Promise secureResponse(MessageInfoContext messageInfo, Subject serviceSubject) { return newResultPromise(SEND_SUCCESS); } - /** - * {@inheritDoc} - */ @Override public Promise cleanSubject(MessageInfoContext messageInfo, Subject subject) { return newResultPromise(null); } + + /** + * Extract SPNEGO token from the specified negotiate authorization header. + * @param header Authorization header to extract SPNEGO token. + * @return Extracted token or null when extraction fails. + */ + private byte[] extractSpnegoToken(String header) { + try { + return Base64.decode(header.substring(NEGOTIATE_AUTH_SCHEME.length()).trim()); + } catch (Exception e) { + LOG.error("IWAModule: Failed to extract SPNEGO token from authorization header"); + return null; + } + } + } diff --git a/auth-filters/forgerock-authn-filter/forgerock-jaspi-modules/forgerock-jaspi-iwa-module/src/main/java/org/forgerock/jaspi/modules/iwa/wdsso/WDSSO.java b/auth-filters/forgerock-authn-filter/forgerock-jaspi-modules/forgerock-jaspi-iwa-module/src/main/java/org/forgerock/jaspi/modules/iwa/wdsso/WDSSO.java index 9497cf6d3..33b0e7951 100644 --- a/auth-filters/forgerock-authn-filter/forgerock-jaspi-modules/forgerock-jaspi-iwa-module/src/main/java/org/forgerock/jaspi/modules/iwa/wdsso/WDSSO.java +++ b/auth-filters/forgerock-authn-filter/forgerock-jaspi-modules/forgerock-jaspi-iwa-module/src/main/java/org/forgerock/jaspi/modules/iwa/wdsso/WDSSO.java @@ -26,6 +26,7 @@ * $Id: WindowsDesktopSSO.java,v 1.7 2009/07/28 19:40:45 beomsuk Exp $ * * Portions Copyrighted 2011-2016 ForgeRock AS. + * Portions Copyright 2024 Wren Security */ package org.forgerock.jaspi.modules.iwa.wdsso; @@ -44,7 +45,6 @@ import javax.security.auth.login.Configuration; import javax.security.auth.login.LoginContext; -import org.forgerock.http.protocol.Request; import org.forgerock.services.context.AttributesContext; import org.forgerock.services.context.Context; import org.ietf.jgss.GSSContext; @@ -54,7 +54,7 @@ import org.ietf.jgss.GSSName; /** - * Windows Desktop Single Sign On implementation, extracted from OpenAM. + * Windows Desktop Single Sign On implementation. */ public class WDSSO { @@ -66,23 +66,9 @@ public class WDSSO { private String kdcServer = null; private boolean returnRealm = false; - /** - * Constructor. - */ public WDSSO() { } - /** - * Initialize parameters. - * - * @param subject The subject. - * @param sharedState The shared state. - * @param options The options. - */ - public void init(Subject subject, Map sharedState, Map options) { - - } - /** * Returns principal of the authenticated user. * @@ -102,21 +88,14 @@ public Principal getPrincipal() { * @return The result. * @throws Exception If an error occurred. */ - public String process(Map options, Context context, Request request) throws Exception { + public String process(Map options, Context context, byte[] spnegoToken) throws Exception { // Check to see if the Rest Auth Endpoint has signified that IWA has failed. if (hasWDSSOFailed(context)) { //TODO this is not required in the context of IB/IDM return "SEND_CONTINUE"; } if (!getConfigParams(options)) { - initWindowsDesktopSSOAuth(options); - } - - // retrieve the spnego token - byte[] spnegoToken = getSPNEGOTokenFromHTTPRequest(request); - if (spnegoToken == null) { - LOG.error("IWA WDSSO: spnego token is not valid."); - throw new RuntimeException(); + initWindowsDesktopSSOAuth(); } // parse the spnego token and extract the kerberos mech token from it @@ -257,23 +236,6 @@ private boolean hasWDSSOFailed(Context context) { return Boolean.valueOf((String) context.asContext(AttributesContext.class).getAttributes().get("iwa-failed")); } - private byte[] getSPNEGOTokenFromHTTPRequest(Request req) { - byte[] spnegoToken = null; - String header = req.getHeaders().getFirst("Authorization"); - if ((header != null) && header.startsWith("Negotiate")) { - header = header.substring("Negotiate".length()).trim(); - LOG.debug("IWA WDSSO: \"Authorization\" header set, {}", header); - try { - spnegoToken = Base64.decode(header); - } catch (Exception e) { - LOG.error("IWA WDSSO: Failed to get SPNEGO Token from request"); - } - } else { - LOG.error("IWA WDSSO: \"Authorization\" header not set in reqest"); - } - return spnegoToken; - } - private byte[] parseToken(byte[] rawToken) { byte[] token = rawToken; DerValue tmpToken = new DerValue(rawToken); @@ -335,21 +297,21 @@ private byte[] parseToken(byte[] rawToken) { return token; } - private boolean getConfigParams(Map options) { + private boolean getConfigParams(Map options) { // KDC realm in service principal must be uppercase. - servicePrincipalName = options.get("servicePrincipal"); - keyTabFile = options.get("keytabFileName"); - kdcRealm = options.get("kerberosRealm"); - kdcServer = options.get("kerberosServerName"); + servicePrincipalName = (String) options.get("servicePrincipal"); + keyTabFile = (String) options.get("keytabFileName"); + kdcRealm = (String) options.get("kerberosRealm"); + kdcServer = (String) options.get("kerberosServerName"); if (options.get("returnRealm") != null) { - returnRealm = Boolean.valueOf(options.get("returnRealm")); + returnRealm = Boolean.valueOf((String) options.get("returnRealm")); } LOG.debug("IWA WDSSO: WindowsDesktopSSO params: principal: {}, keytab file: {}, realm : {}, kdc server: {}, returnRealm: {}", servicePrincipalName, keyTabFile, kdcRealm, kdcServer, returnRealm); return serviceSubject != null; } - private void initWindowsDesktopSSOAuth(Map options) throws Exception { + private void initWindowsDesktopSSOAuth() throws Exception { verifyAttributes(); serviceLogin(); } From bc58b238fde58fbd8c718a128e458aa5ce1589f9 Mon Sep 17 00:00:00 2001 From: Karel Maxa Date: Tue, 10 Dec 2024 15:01:12 +0100 Subject: [PATCH 2/2] Ignore NTLM in IWA module (#55) --- .../main/java/org/forgerock/jaspi/modules/iwa/IWAModule.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/auth-filters/forgerock-authn-filter/forgerock-jaspi-modules/forgerock-jaspi-iwa-module/src/main/java/org/forgerock/jaspi/modules/iwa/IWAModule.java b/auth-filters/forgerock-authn-filter/forgerock-jaspi-modules/forgerock-jaspi-iwa-module/src/main/java/org/forgerock/jaspi/modules/iwa/IWAModule.java index 4fd18a218..6a50fdf37 100644 --- a/auth-filters/forgerock-authn-filter/forgerock-jaspi-modules/forgerock-jaspi-iwa-module/src/main/java/org/forgerock/jaspi/modules/iwa/IWAModule.java +++ b/auth-filters/forgerock-authn-filter/forgerock-jaspi-modules/forgerock-jaspi-iwa-module/src/main/java/org/forgerock/jaspi/modules/iwa/IWAModule.java @@ -103,6 +103,11 @@ public Promise validateRequest(MessageInfoC return newExceptionPromise(new AuthenticationException("Invalid SPNEGO token")); } + // Ignore NTLM over SPNEGO + if (spnegoToken[0] == 'N' && spnegoToken[1] == 'T' && spnegoToken[2] == 'L' && spnegoToken[3] == 'M') { + return newResultPromise(SEND_FAILURE); + } + // Perform Kerberos authentication try { final String username = new WDSSO().process(options, messageInfo, spnegoToken);