Skip to content

Commit

Permalink
[ELY-2359] Support HTTP Digest when fronted by load balancer
Browse files Browse the repository at this point in the history
  • Loading branch information
Skyllarr committed May 8, 2024
1 parent 6522e83 commit b260766
Show file tree
Hide file tree
Showing 9 changed files with 555 additions and 143 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public final class MechanismConfiguration {
private final Map<String, MechanismRealmConfiguration> mechanismRealms;
private final CredentialSource serverCredentialSource;

MechanismConfiguration(final Function<Principal, Principal> preRealmRewriter, final Function<Principal, Principal> postRealmRewriter, final Function<Principal, Principal> finalRewriter, final RealmMapper realmMapper, final Collection<MechanismRealmConfiguration> mechanismRealms, final CredentialSource serverCredentialSource) {
MechanismConfiguration(final Function<Principal, Principal> preRealmRewriter, final Function<Principal, Principal> postRealmRewriter, final Function<Principal, Principal> finalRewriter, final RealmMapper realmMapper, final Collection<MechanismRealmConfiguration> mechanismRealms, final CredentialSource serverCredentialSource, final boolean sessionBasedNonceManager) {
checkNotNullParam("mechanismRealms", mechanismRealms);
this.preRealmRewriter = preRealmRewriter;
this.postRealmRewriter = postRealmRewriter;
Expand Down Expand Up @@ -167,6 +167,7 @@ public static final class Builder {
private RealmMapper realmMapper;
private List<MechanismRealmConfiguration> mechanismRealms;
private CredentialSource serverCredentialSource = CredentialSource.NONE;
private boolean useSessionBasedNonceManager = false;

/**
* Construct a new instance.
Expand Down Expand Up @@ -271,6 +272,12 @@ public Builder setServerCredentialSource(final CredentialSource serverCredential
return this;
}

public Builder setUseSessionBasedNonceManager(final Boolean useSessionBasedNonceManager) {
checkNotNullParam("useSessionBasedNonceManager", useSessionBasedNonceManager);
this.useSessionBasedNonceManager = useSessionBasedNonceManager;
return this;
}

/**
* Build a new instance. If no mechanism realms are offered, an empty collection should be provided for
* {@code mechanismRealms}; otherwise, if the mechanism only supports one realm, the first will be used. If the
Expand All @@ -285,12 +292,12 @@ public MechanismConfiguration build() {
} else {
mechanismRealms = unmodifiableList(asList(mechanismRealms.toArray(NO_REALM_CONFIGS)));
}
return new MechanismConfiguration(preRealmRewriter, postRealmRewriter, finalRewriter, realmMapper, mechanismRealms, serverCredentialSource);
return new MechanismConfiguration(preRealmRewriter, postRealmRewriter, finalRewriter, realmMapper, mechanismRealms, serverCredentialSource, useSessionBasedNonceManager);
}
}

/**
* An empty mechanism configuration..
*/
public static final MechanismConfiguration EMPTY = new MechanismConfiguration(Function.identity(), Function.identity(), Function.identity(), null, emptyList(), CredentialSource.NONE);
public static final MechanismConfiguration EMPTY = new MechanismConfiguration(Function.identity(), Function.identity(), Function.identity(), null, emptyList(), CredentialSource.NONE, false);
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ private HttpConstants() {

public static final String CONFIG_VALIDATE_DIGEST_URI = CONFIG_BASE + ".validate-digest-uri";
public static final String CONFIG_SKIP_CERTIFICATE_VERIFICATION = CONFIG_BASE + ".skip-certificate-verification";
public static final String CONFIG_SESSION_BASED_DIGEST_NONCE_MANAGER = CONFIG_BASE + ".use-session-based-digest-nonce-manager";

/**
* The context relative path of the login page.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
import org.wildfly.security.http.HttpServerMechanismsResponder;
import org.wildfly.security.http.HttpServerRequest;
import org.wildfly.security.http.HttpServerResponse;
import org.wildfly.security.http.Scope;
import org.wildfly.security.mechanism.AuthenticationMechanismException;
import org.wildfly.security.mechanism.digest.DigestQuote;
import org.wildfly.security.mechanism.digest.PasswordDigestObtainer;
Expand All @@ -82,10 +83,11 @@ final class DigestAuthenticationMechanism implements HttpServerAuthenticationMec
private static final String CHALLENGE_PREFIX = "Digest ";
private static final String OPAQUE_VALUE = "00000000000000000000000000000000";
private static final byte COLON = ':';
public static final String PERSISTENT_NONCE_MANAGER = "persistentNonceManager";

private final Supplier<Provider[]> providers;
private final CallbackHandler callbackHandler;
private final NonceManager nonceManager;
private NonceManager nonceManager;
private final String configuredRealm;
private final String domain;
private final String mechanismName;
Expand Down Expand Up @@ -116,6 +118,22 @@ public String getMechanismName() {

@Override
public void evaluateRequest(final HttpServerRequest request) throws HttpAuthenticationException {

// TODO can we pass HttpServerRequest to the NonceManager so it interacts directly
if (nonceManager instanceof PersistentNonceManager) {
if (request.getScope(Scope.SESSION) == null || !request.getScope(Scope.SESSION).exists()) {
request.getScope(Scope.SESSION).create();
}
PersistentNonceManager persistentNonceManager = (PersistentNonceManager) request.getScope(Scope.SESSION).getAttachment(PERSISTENT_NONCE_MANAGER);
if (persistentNonceManager != null) {
// executor is transient, so it will be always be null
if (persistentNonceManager.getExecutor() == null) {
persistentNonceManager.setDefaultExecutor();
}
nonceManager = persistentNonceManager;
}
}

List<String> authorizationValues = request.getRequestHeaderValues(AUTHORIZATION);

if (authorizationValues != null) {
Expand All @@ -128,14 +146,13 @@ public void evaluateRequest(final HttpServerRequest request) throws HttpAuthenti
return;
} catch (AuthenticationMechanismException e) {
httpDigest.trace("Failed to parse or validate the response", e);
request.badRequest(e.toHttpAuthenticationException(), response -> prepareResponse(selectRealm(), response, false));
request.badRequest(e.toHttpAuthenticationException(), response -> prepareResponse(selectRealm(), response, false, request));
return;
}
}
}
}

request.noAuthenticationInProgress(response -> prepareResponse(selectRealm(), response, false));
request.noAuthenticationInProgress(response -> prepareResponse(selectRealm(), response, false, request));
}

private void validateResponse(HashMap<String, byte[]> responseTokens, final HttpServerRequest request) throws AuthenticationMechanismException, HttpAuthenticationException {
Expand Down Expand Up @@ -213,7 +230,7 @@ private void validateResponse(HashMap<String, byte[]> responseTokens, final Http
if (username.length() == 0) {
httpDigest.trace("Failed: no username");
fail();
request.authenticationFailed(httpDigest.authenticationFailed(), httpResponse -> prepareResponse(selectedRealm, httpResponse, false));
request.authenticationFailed(httpDigest.authenticationFailed(), httpResponse -> prepareResponse(selectedRealm, httpResponse, false, request));
return;
}

Expand All @@ -222,7 +239,7 @@ private void validateResponse(HashMap<String, byte[]> responseTokens, final Http
if (hA1 == null) {
httpDigest.trace("Failed: unable to get expected proof");
fail();
request.authenticationFailed(httpDigest.authenticationFailed(), httpResponse -> prepareResponse(selectedRealm, httpResponse, false));
request.authenticationFailed(httpDigest.authenticationFailed(), httpResponse -> prepareResponse(selectedRealm, httpResponse, false, request));
return;
}

Expand All @@ -231,13 +248,13 @@ private void validateResponse(HashMap<String, byte[]> responseTokens, final Http
if (MessageDigest.isEqual(response, calculatedResponse) == false) {
httpDigest.trace("Failed: invalid proof");
fail();
request.authenticationFailed(httpDigest.mechResponseTokenMismatch(getMechanismName()), httpResponse -> prepareResponse(selectedRealm, httpResponse, false));
request.authenticationFailed(httpDigest.mechResponseTokenMismatch(getMechanismName()), httpResponse -> prepareResponse(selectedRealm, httpResponse, false, request));
return;
}

if (nonceValid == false) {
httpDigest.trace("Failed: invalid nonce");
request.authenticationInProgress(httpResponse -> prepareResponse(selectedRealm, httpResponse, true));
request.authenticationInProgress(httpResponse -> prepareResponse(selectedRealm, httpResponse, true, request));
return;
}

Expand Down Expand Up @@ -390,7 +407,7 @@ private String[] getAvailableRealms() throws AuthenticationMechanismException {
}
}

private void prepareResponse(String realmName, HttpServerResponse response, boolean stale) throws HttpAuthenticationException {
private void prepareResponse(String realmName, HttpServerResponse response, boolean stale, HttpServerRequest request) throws HttpAuthenticationException {
StringBuilder sb = new StringBuilder(CHALLENGE_PREFIX);
sb.append(REALM).append("=\"").append(DigestQuote.quote(realmName)).append("\"");

Expand All @@ -407,6 +424,10 @@ private void prepareResponse(String realmName, HttpServerResponse response, bool

response.addResponseHeader(WWW_AUTHENTICATE, sb.toString());
response.setStatusCode(UNAUTHORIZED);

if ((nonceManager instanceof PersistentNonceManager) && request.getScope(Scope.SESSION) != null) {
request.getScope(Scope.SESSION).setAttachment(PERSISTENT_NONCE_MANAGER, this.nonceManager);
}
}

private boolean authorize(String username) throws AuthenticationMechanismException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import static org.wildfly.common.Assert.checkNotNullParam;
import static org.wildfly.security.http.HttpConstants.CONFIG_CONTEXT_PATH;
import static org.wildfly.security.http.HttpConstants.CONFIG_REALM;
import static org.wildfly.security.http.HttpConstants.CONFIG_SESSION_BASED_DIGEST_NONCE_MANAGER;
import static org.wildfly.security.http.HttpConstants.DIGEST_NAME;
import static org.wildfly.security.http.HttpConstants.DIGEST_SHA256_NAME;
import static org.wildfly.security.http.HttpConstants.DIGEST_SHA512_256_NAME;
Expand Down Expand Up @@ -58,7 +59,7 @@ public DigestMechanismFactory() {
}

public DigestMechanismFactory(final Provider provider) {
this(new Provider[] { provider });
this(new Provider[]{provider});
}

public DigestMechanismFactory(final Provider... providers) {
Expand Down Expand Up @@ -98,8 +99,13 @@ public HttpServerAuthenticationMechanism createAuthenticationMechanism(String me
checkNotNullParam("properties", properties);
checkNotNullParam("callbackHandler", callbackHandler);

// TODO can I properties this with the PersistentNonceManager
if (properties.containsKey("nonceManager")) {
nonceManager = (NonceManager) properties.get("nonceManager");
} else if (properties.get(CONFIG_SESSION_BASED_DIGEST_NONCE_MANAGER) != null) {
if (Boolean.parseBoolean((String) properties.get(CONFIG_SESSION_BASED_DIGEST_NONCE_MANAGER))) {
nonceManager = new PersistentNonceManager(NonceManagerUtils.DEFAULT_VALIDITY_PERIOD, NonceManagerUtils.DEFAULT_NONCE_SESSION_TIME, true, NonceManagerUtils.DEFAULT_KEY_SIZE, SHA256, ElytronMessages.httpDigest);
}
}

switch (mechanismName) {
Expand Down
Loading

0 comments on commit b260766

Please sign in to comment.