Skip to content

Commit

Permalink
Merge pull request #56 from karelmaxa/iwa-ignore-non-negotiate
Browse files Browse the repository at this point in the history
 Ignore NTLM in IWA module
  • Loading branch information
pavelhoral authored Dec 11, 2024
2 parents f996178 + bc58b23 commit 08eedc1
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 112 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;

Expand All @@ -49,106 +49,105 @@
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<String, Object> options;

@Override
public String getModuleId() {
return "IWA";
}

/**
* {@inheritDoc}
*/
@Override
public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, CallbackHandler handler,
Map<String, Object> options) throws AuthenticationException {
this.handler = handler;
this.options = options;
}

/**
* {@inheritDoc}
*/
@Override
public Collection<Class<?>> 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<AuthStatus, AuthenticationException> 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"));
}

// 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 {
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<String, Object> 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<AuthStatus, AuthenticationException> secureResponse(MessageInfoContext messageInfo,
Subject serviceSubject) {
public Promise<AuthStatus, AuthenticationException> secureResponse(MessageInfoContext messageInfo, Subject serviceSubject) {
return newResultPromise(SEND_SUCCESS);
}

/**
* {@inheritDoc}
*/
@Override
public Promise<Void, AuthenticationException> 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;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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 {

Expand All @@ -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.
*
Expand All @@ -102,21 +88,14 @@ public Principal getPrincipal() {
* @return The result.
* @throws Exception If an error occurred.
*/
public String process(Map<String, String> options, Context context, Request request) throws Exception {
public String process(Map<String, Object> 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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -335,21 +297,21 @@ private byte[] parseToken(byte[] rawToken) {
return token;
}

private boolean getConfigParams(Map<String, String> options) {
private boolean getConfigParams(Map<String, Object> 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();
}
Expand Down

0 comments on commit 08eedc1

Please sign in to comment.