Skip to content

Commit 08eedc1

Browse files
authored
Merge pull request #56 from karelmaxa/iwa-ignore-non-negotiate
Ignore NTLM in IWA module
2 parents f996178 + bc58b23 commit 08eedc1

File tree

2 files changed

+73
-112
lines changed
  • auth-filters/forgerock-authn-filter/forgerock-jaspi-modules/forgerock-jaspi-iwa-module/src/main/java/org/forgerock/jaspi/modules/iwa

2 files changed

+73
-112
lines changed

auth-filters/forgerock-authn-filter/forgerock-jaspi-modules/forgerock-jaspi-iwa-module/src/main/java/org/forgerock/jaspi/modules/iwa/IWAModule.java

+62-63
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
* information: "Portions copyright [year] [name of copyright owner]".
1313
*
1414
* Copyright 2013-2016 ForgeRock AS.
15+
* Portions Copyright 2024 Wren Security
1516
*/
1617

1718
package org.forgerock.jaspi.modules.iwa;
@@ -23,13 +24,11 @@
2324

2425
import javax.security.auth.Subject;
2526
import javax.security.auth.callback.CallbackHandler;
26-
import javax.security.auth.message.AuthException;
2727
import javax.security.auth.message.AuthStatus;
2828
import javax.security.auth.message.MessagePolicy;
2929
import java.security.Principal;
3030
import java.util.Arrays;
3131
import java.util.Collection;
32-
import java.util.HashMap;
3332
import java.util.Map;
3433

3534
import org.forgerock.caf.authentication.api.AsyncServerAuthModule;
@@ -38,6 +37,7 @@
3837
import org.forgerock.http.protocol.Request;
3938
import org.forgerock.http.protocol.Response;
4039
import org.forgerock.http.protocol.Status;
40+
import org.forgerock.jaspi.modules.iwa.wdsso.Base64;
4141
import org.forgerock.jaspi.modules.iwa.wdsso.WDSSO;
4242
import org.forgerock.util.promise.Promise;
4343

@@ -49,106 +49,105 @@
4949
public class IWAModule implements AsyncServerAuthModule {
5050

5151
private static final String IWA_FAILED = "iwa-failed";
52+
private static final String NEGOTIATE_AUTH_SCHEME = "Negotiate";
5253

53-
private CallbackHandler handler;
54-
private Map options;
54+
private Map<String, Object> options;
5555

5656
@Override
5757
public String getModuleId() {
5858
return "IWA";
5959
}
6060

61-
/**
62-
* {@inheritDoc}
63-
*/
6461
@Override
6562
public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, CallbackHandler handler,
6663
Map<String, Object> options) throws AuthenticationException {
67-
this.handler = handler;
6864
this.options = options;
6965
}
7066

71-
/**
72-
* {@inheritDoc}
73-
*/
7467
@Override
7568
public Collection<Class<?>> getSupportedMessageTypes() {
76-
return Arrays.asList(new Class<?>[]{Request.class, Response.class});
69+
return Arrays.asList(new Class<?>[]{ Request.class, Response.class });
7770
}
7871

7972
/**
8073
* Validates the request by checking the Authorization header in the request for a IWA token and processes that
8174
* for authentication.
82-
*
83-
* @param messageInfo {@inheritDoc}
84-
* @param clientSubject {@inheritDoc}
85-
* @param serviceSubject {@inheritDoc}
86-
* @return {@inheritDoc}
8775
*/
8876
@Override
8977
public Promise<AuthStatus, AuthenticationException> validateRequest(MessageInfoContext messageInfo,
9078
Subject clientSubject, Subject serviceSubject) {
91-
92-
LOG.debug("IWAModule: validateRequest START");
93-
9479
Request request = messageInfo.getRequest();
9580
Response response = messageInfo.getResponse();
9681

97-
String httpAuthorization = request.getHeaders().getFirst("Authorization");
82+
String authorizationHeader = request.getHeaders().getFirst("Authorization");
9883

84+
if (authorizationHeader == null || authorizationHeader.isEmpty()) {
85+
LOG.debug("IWAModule: Authorization Header NOT set in request.");
86+
87+
response.getHeaders().put("WWW-Authenticate", NEGOTIATE_AUTH_SCHEME);
88+
response.setStatus(Status.UNAUTHORIZED);
89+
response.setEntity(Map.of("failure", true, "recason", IWA_FAILED));
90+
return newResultPromise(SEND_CONTINUE);
91+
}
92+
93+
// Handle only negotiate authentication requests
94+
if (!authorizationHeader.startsWith(NEGOTIATE_AUTH_SCHEME)) {
95+
return newResultPromise(SEND_FAILURE);
96+
}
97+
98+
LOG.debug("IWAModule: Negotiate authorization header set in request.");
99+
100+
// Check SPNEGO token
101+
byte[] spnegoToken = extractSpnegoToken(authorizationHeader);
102+
if (spnegoToken == null) {
103+
return newExceptionPromise(new AuthenticationException("Invalid SPNEGO token"));
104+
}
105+
106+
// Ignore NTLM over SPNEGO
107+
if (spnegoToken[0] == 'N' && spnegoToken[1] == 'T' && spnegoToken[2] == 'L' && spnegoToken[3] == 'M') {
108+
return newResultPromise(SEND_FAILURE);
109+
}
110+
111+
// Perform Kerberos authentication
99112
try {
100-
if (httpAuthorization == null || "".equals(httpAuthorization)) {
101-
LOG.debug("IWAModule: Authorization Header NOT set in request.");
102-
103-
response.getHeaders().put("WWW-Authenticate", "Negotiate");
104-
response.setStatus(Status.UNAUTHORIZED);
105-
Map<String, Object> entity = new HashMap<>();
106-
entity.put("failure", true);
107-
entity.put("recason", IWA_FAILED);
108-
response.setEntity(entity);
109-
110-
return newResultPromise(SEND_CONTINUE);
111-
} else {
112-
LOG.debug("IWAModule: Authorization Header set in request.");
113-
try {
114-
final String username = new WDSSO().process(options, messageInfo, request);
115-
LOG.debug("IWAModule: IWA successful with username, {}", username);
116-
117-
clientSubject.getPrincipals().add(new Principal() {
118-
public String getName() {
119-
return username;
120-
}
121-
});
122-
} catch (Exception e) {
123-
LOG.debug("IWAModule: IWA has failed. {}", e.getMessage());
124-
return newExceptionPromise(new AuthenticationException("IWA has failed"));
125-
}
113+
final String username = new WDSSO().process(options, messageInfo, spnegoToken);
114+
LOG.debug("IWAModule: IWA successful with username, {}", username);
126115

127-
return newResultPromise(SUCCESS);
128-
}
129-
} finally {
130-
LOG.debug("IWAModule: validateRequest END");
116+
clientSubject.getPrincipals().add(new Principal() {
117+
@Override
118+
public String getName() {
119+
return username;
120+
}
121+
});
122+
return newResultPromise(SUCCESS);
123+
} catch (Exception e) {
124+
LOG.debug("IWAModule: IWA has failed. {}", e.getMessage());
125+
return newExceptionPromise(new AuthenticationException("IWA has failed"));
131126
}
132127
}
133128

134-
/**
135-
* Always returns AuthStatus.SEND_SUCCESS.
136-
*
137-
* @param messageInfo {@inheritDoc}
138-
* @param serviceSubject {@inheritDoc}
139-
* @return {@inheritDoc}
140-
*/
141129
@Override
142-
public Promise<AuthStatus, AuthenticationException> secureResponse(MessageInfoContext messageInfo,
143-
Subject serviceSubject) {
130+
public Promise<AuthStatus, AuthenticationException> secureResponse(MessageInfoContext messageInfo, Subject serviceSubject) {
144131
return newResultPromise(SEND_SUCCESS);
145132
}
146133

147-
/**
148-
* {@inheritDoc}
149-
*/
150134
@Override
151135
public Promise<Void, AuthenticationException> cleanSubject(MessageInfoContext messageInfo, Subject subject) {
152136
return newResultPromise(null);
153137
}
138+
139+
/**
140+
* Extract SPNEGO token from the specified negotiate authorization header.
141+
* @param header Authorization header to extract SPNEGO token.
142+
* @return Extracted token or null when extraction fails.
143+
*/
144+
private byte[] extractSpnegoToken(String header) {
145+
try {
146+
return Base64.decode(header.substring(NEGOTIATE_AUTH_SCHEME.length()).trim());
147+
} catch (Exception e) {
148+
LOG.error("IWAModule: Failed to extract SPNEGO token from authorization header");
149+
return null;
150+
}
151+
}
152+
154153
}

auth-filters/forgerock-authn-filter/forgerock-jaspi-modules/forgerock-jaspi-iwa-module/src/main/java/org/forgerock/jaspi/modules/iwa/wdsso/WDSSO.java

+11-49
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
* $Id: WindowsDesktopSSO.java,v 1.7 2009/07/28 19:40:45 beomsuk Exp $
2727
*
2828
* Portions Copyrighted 2011-2016 ForgeRock AS.
29+
* Portions Copyright 2024 Wren Security
2930
*/
3031

3132
package org.forgerock.jaspi.modules.iwa.wdsso;
@@ -44,7 +45,6 @@
4445
import javax.security.auth.login.Configuration;
4546
import javax.security.auth.login.LoginContext;
4647

47-
import org.forgerock.http.protocol.Request;
4848
import org.forgerock.services.context.AttributesContext;
4949
import org.forgerock.services.context.Context;
5050
import org.ietf.jgss.GSSContext;
@@ -54,7 +54,7 @@
5454
import org.ietf.jgss.GSSName;
5555

5656
/**
57-
* Windows Desktop Single Sign On implementation, extracted from OpenAM.
57+
* Windows Desktop Single Sign On implementation.
5858
*/
5959
public class WDSSO {
6060

@@ -66,23 +66,9 @@ public class WDSSO {
6666
private String kdcServer = null;
6767
private boolean returnRealm = false;
6868

69-
/**
70-
* Constructor.
71-
*/
7269
public WDSSO() {
7370
}
7471

75-
/**
76-
* Initialize parameters.
77-
*
78-
* @param subject The subject.
79-
* @param sharedState The shared state.
80-
* @param options The options.
81-
*/
82-
public void init(Subject subject, Map sharedState, Map options) {
83-
84-
}
85-
8672
/**
8773
* Returns principal of the authenticated user.
8874
*
@@ -102,21 +88,14 @@ public Principal getPrincipal() {
10288
* @return The result.
10389
* @throws Exception If an error occurred.
10490
*/
105-
public String process(Map<String, String> options, Context context, Request request) throws Exception {
91+
public String process(Map<String, Object> options, Context context, byte[] spnegoToken) throws Exception {
10692
// Check to see if the Rest Auth Endpoint has signified that IWA has failed.
10793
if (hasWDSSOFailed(context)) { //TODO this is not required in the context of IB/IDM
10894
return "SEND_CONTINUE";
10995
}
11096

11197
if (!getConfigParams(options)) {
112-
initWindowsDesktopSSOAuth(options);
113-
}
114-
115-
// retrieve the spnego token
116-
byte[] spnegoToken = getSPNEGOTokenFromHTTPRequest(request);
117-
if (spnegoToken == null) {
118-
LOG.error("IWA WDSSO: spnego token is not valid.");
119-
throw new RuntimeException();
98+
initWindowsDesktopSSOAuth();
12099
}
121100

122101
// parse the spnego token and extract the kerberos mech token from it
@@ -257,23 +236,6 @@ private boolean hasWDSSOFailed(Context context) {
257236
return Boolean.valueOf((String) context.asContext(AttributesContext.class).getAttributes().get("iwa-failed"));
258237
}
259238

260-
private byte[] getSPNEGOTokenFromHTTPRequest(Request req) {
261-
byte[] spnegoToken = null;
262-
String header = req.getHeaders().getFirst("Authorization");
263-
if ((header != null) && header.startsWith("Negotiate")) {
264-
header = header.substring("Negotiate".length()).trim();
265-
LOG.debug("IWA WDSSO: \"Authorization\" header set, {}", header);
266-
try {
267-
spnegoToken = Base64.decode(header);
268-
} catch (Exception e) {
269-
LOG.error("IWA WDSSO: Failed to get SPNEGO Token from request");
270-
}
271-
} else {
272-
LOG.error("IWA WDSSO: \"Authorization\" header not set in reqest");
273-
}
274-
return spnegoToken;
275-
}
276-
277239
private byte[] parseToken(byte[] rawToken) {
278240
byte[] token = rawToken;
279241
DerValue tmpToken = new DerValue(rawToken);
@@ -335,21 +297,21 @@ private byte[] parseToken(byte[] rawToken) {
335297
return token;
336298
}
337299

338-
private boolean getConfigParams(Map<String, String> options) {
300+
private boolean getConfigParams(Map<String, Object> options) {
339301
// KDC realm in service principal must be uppercase.
340-
servicePrincipalName = options.get("servicePrincipal");
341-
keyTabFile = options.get("keytabFileName");
342-
kdcRealm = options.get("kerberosRealm");
343-
kdcServer = options.get("kerberosServerName");
302+
servicePrincipalName = (String) options.get("servicePrincipal");
303+
keyTabFile = (String) options.get("keytabFileName");
304+
kdcRealm = (String) options.get("kerberosRealm");
305+
kdcServer = (String) options.get("kerberosServerName");
344306
if (options.get("returnRealm") != null) {
345-
returnRealm = Boolean.valueOf(options.get("returnRealm"));
307+
returnRealm = Boolean.valueOf((String) options.get("returnRealm"));
346308
}
347309
LOG.debug("IWA WDSSO: WindowsDesktopSSO params: principal: {}, keytab file: {}, realm : {}, kdc server: {}, returnRealm: {}",
348310
servicePrincipalName, keyTabFile, kdcRealm, kdcServer, returnRealm);
349311
return serviceSubject != null;
350312
}
351313

352-
private void initWindowsDesktopSSOAuth(Map options) throws Exception {
314+
private void initWindowsDesktopSSOAuth() throws Exception {
353315
verifyAttributes();
354316
serviceLogin();
355317
}

0 commit comments

Comments
 (0)