Skip to content

[ELY-2359] Support HTTP Digest when fronted by load balancer#1909

Closed
skyllarr wants to merge 3 commits intowildfly-security:2.xfrom
skyllarr:ely-2359
Closed

[ELY-2359] Support HTTP Digest when fronted by load balancer#1909
skyllarr wants to merge 3 commits intowildfly-security:2.xfrom
skyllarr:ely-2359

Conversation

@skyllarr
Copy link
Copy Markdown
Contributor

@skyllarr
Copy link
Copy Markdown
Contributor Author

skyllarr commented Sep 5, 2023

@fjuma This can be reviewed, tests are in the wilfdly PR as they need to setup clustering. Thank you!

Copy link
Copy Markdown
Contributor

@fjuma fjuma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @skyllarr! I've added some initial comments.


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_DIGEST = CONFIG_BASE + ".session-digest";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to make it more clear that this is a boolean value. Maybe something like use-digest-session-based-nonce-manager or use-digest-session-nonce-manager or use-session-digest, etc.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fjuma Thank you! Fixed to be use-session-based-digest-nonce-manager

return mechanismRealms.get(realmName);
}

public boolean getSessionDigest() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a minor comment on naming, since this is returning a boolean value, we could rename this to something like isUseSessionDigest or getUseSessionDigest (or something else depending on the property name you go with).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fjuma Fixed

return this;
}

public Builder setSessionDigest(final Boolean sessionDigest) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, would be good to rename this too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Object configSessionDigest = properties.get(CONFIG_SESSION_DIGEST);
String sessionDigest = configSessionDigest instanceof String ? (String) configSessionDigest : null;
if (Boolean.parseBoolean(sessionDigest)) {
nonceManager = new PersistentNonceManager(300000, 900000, true, 20, SHA256, ElytronMessages.httpDigest);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be good to make these parameter values constants so they can be shared with the default NonceManager too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fjuma Thank you! fixed by placing the constants to the NonceManagerUtils

Comment thread http/digest/src/main/java/org/wildfly/security/http/digest/NonceManager.java Outdated
} catch (GeneralSecurityException e) {
throw new IllegalStateException(e);
}
return NonceManagerUtils.generateNonce(salt, algorithm, nonceCounter, privateKey );
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally minor, there's an extra space before the closing bracket.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, thanks!

* @param algorithm the message digest algorithm to use when creating the digest portion of the nonce.
*/

@Deprecated
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to check, why is this deprecated?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fjuma This constructor was deprecated because its associated parent constructor is deprecated. I will remove this constructor instead of adding it as deprecated

/*
* JBoss, Home of Professional Open Source.
* Copyright 2018 Red Hat, Inc., and individual contributors
* Copyright 2023 Red Hat, Inc., and individual contributors
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious why this is change is needed. I don't seem to see other updates in this file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, thanks!

if (request.getScope(Scope.SESSION) == null || !request.getScope(Scope.SESSION).exists()) {
request.getScope(Scope.SESSION).create();
}
PersistentNonceManager persistentNonceManager = (PersistentNonceManager) request.getScope(Scope.SESSION).getAttachment("persistentNonceManager");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to make the "persistentNonceManager" key a constant.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, thanks!

*
* @param persistentNonceManager PersistentNonceManager instance to update the attributes from
*/
void refreshInfoFromSessionNonceManager(PersistentNonceManager persistentNonceManager) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just so I understand better, would you be able to provide some details on why this is needed? Wondering if it's possible to make use of the nonce manager from the session directly rather than updating the attributes of another instance.

Copy link
Copy Markdown
Contributor Author

@skyllarr skyllarr Dec 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fjuma The nonceManager in DigestAuthenticationmechanism, where this method is being used, is a final variable. I need to be retrieving the nonce manager with each request, but I cannot assign to a final variable. So I am assigning only its attributes instead. Should I change the nonceManager to be non final so I can assign to it with every request?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the final from the nonceManager and removed this method. It is now being assigned directly instead of updating specific attributes

@skyllarr
Copy link
Copy Markdown
Contributor Author

skyllarr commented Dec 5, 2023

@darranl Please review, thanks!

} else if (properties.get(CONFIG_SESSION_BASED_DIGEST_NONCE_MANAGER) != null) {
Object configSessionDigest = properties.get(CONFIG_SESSION_BASED_DIGEST_NONCE_MANAGER);
String sessionDigest = configSessionDigest instanceof String ? (String) configSessionDigest : null;
if (Boolean.parseBoolean(sessionDigest)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Boolean.parseBoolean((String) properties.get(CONFIG_SESSION_BASED_DIGEST_NONCE_MANAGER)) could be used here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated, thanks!

* @param log mechanism specific logger.
*/
PersistentNonceManager(long validityPeriod, long nonceSessionTime, boolean singleUse, int keySize, String algorithm, ElytronMessages log) {
this.validityPeriodNano = validityPeriod * 1000000;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just minor but looks like this constructor could just call the one below with customExecutor set to null.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

private long nonceSessionTime;
private boolean singleUse;
private String algorithm;
private volatile ElytronMessages log;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this volatile?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fjuma Not sure why this was volatile, I removed it, thanks!

@fjuma fjuma requested a review from darranl December 6, 2023 22:38
@fjuma
Copy link
Copy Markdown
Contributor

fjuma commented Jan 3, 2024

@darranl When you get a chance, please review, thanks!

@skyllarr skyllarr force-pushed the ely-2359 branch 2 times, most recently from 4605226 to 12b892a Compare January 10, 2024 10:39
@skyllarr
Copy link
Copy Markdown
Contributor Author

@darranl Please review, thank you!

@skyllarr
Copy link
Copy Markdown
Contributor Author

@darranl Please review this one when you get a chance, thank you!

@skyllarr
Copy link
Copy Markdown
Contributor Author

skyllarr commented Mar 7, 2024

@darranl Please review this one when you get a chance, thank you!

@darranl
Copy link
Copy Markdown
Contributor

darranl commented Mar 8, 2024

I am trying to unravel some of the history regarding the NonceManager, firstly I think I would say in this case we probably don't need to read too much into the variable being final - if at the time these classes were implemented we were setting it once and never changing it we would have set it to final more to allow the compiler to verify we set it to comething and will not change it - not that it is final so we can never change it.

It looks like the package is considered private API so I think this has the option to do what we want here rather than constrain ourselves with the past.

I see Ashley's changes also added the option to pass in a NonceManager but searching the code I can't find us using this yet.

So maybe we could go a few steps further:

  1. Define a NonceManager interface / API in a public package with the methods we need including Ashley's fix.
  2. Maybe the mechanism can always pass in the HttpServerRequest object so the NonceManager can interact directly?
  3. Our two NonceManager implementations maybe should not be public?

@skyllarr skyllarr force-pushed the ely-2359 branch 4 times, most recently from 414d36f to b260766 Compare May 8, 2024 09:48
@skyllarr
Copy link
Copy Markdown
Contributor Author

skyllarr commented May 8, 2024

Just to post an update here - since this will be persisted within the HTTP session the backwards compatibility will need to be kept in mind

@skyllarr
Copy link
Copy Markdown
Contributor Author

skyllarr commented May 8, 2024

I added some TODOs in the code. The remaining points to address are from Darran above:

Define a NonceManager interface / API in a public package with the methods we need including Ashley's fix.
Maybe the mechanism can always pass in the HttpServerRequest object so the NonceManager can interact directly?
Our two NonceManager implementations maybe should not be public?

@darranl
Copy link
Copy Markdown
Contributor

darranl commented Sep 15, 2024

FYI I am going to be picking up this one to continue, we can keep this PR here for now but I will likely create a new one to target specific branches.

@skyllarr skyllarr force-pushed the ely-2359 branch 2 times, most recently from 6d1b386 to 8b30960 Compare August 1, 2025 12:01
@skyllarr
Copy link
Copy Markdown
Contributor Author

skyllarr commented Aug 1, 2025

It looks like the package is considered private API so I think this has the option to do what we want here rather than constrain ourselves with the past.

IIRC we planned to backport this into older versions, so that was another consideration, but yes since it is private API I updated it as per your suggestion.

I see Ashley's changes also added the option to pass in a NonceManager but searching the code I can't find us using this yet.

@darranl I think you mean this Ashley's PR , which added an option to get nonceManager from the mechanism properties here. Since I am using the nonce manager associated with the HTTP session, I am storing it in the HTTP session as an attachment, not in the properties. I am just checking the properties to know if the nonce manager should be persisted.

2. Maybe the mechanism can always pass in the `HttpServerRequest` object so the NonceManager can interact directly?

PR updated with this change.

3. Our two NonceManager implementations maybe should not be public?

Updated.

@skyllarr skyllarr force-pushed the ely-2359 branch 7 times, most recently from f838ff8 to 7bf43f3 Compare August 7, 2025 16:40
@skyllarr
Copy link
Copy Markdown
Contributor Author

skyllarr commented Aug 8, 2025

This PR is superseded by #2300 which targets 2.2.x. Leaving this opened for reference.

@pferraro
Copy link
Copy Markdown
Contributor

pferraro commented Aug 18, 2025

A few questions about this proposal:

  1. It was my understanding that a NonceManager is application-scoped, given that it is (or was, at least) associated with the DigestAuthenticationMechanism, which is associated with a deployment. The changes here suggest that this is object is now scoped to a user/request, is that right?
    1. DefaultNonceManager contains NonceManager.setRequest(...)/getRequest(...) methods, suggesting it can only be associated with one request at a time?
    2. DefaultNonceManager is Serializable and stored in the HttpSession, meaning that new instances are recreated every time this HttpSession attribute is replicated or re-activated from passivated state.
  2. By storing the DefaultNonceManager in the HttpSession along with other user attributes - the DefaultNonceManager will accessible to applications via the HttpSession interface. Is this intentional? Or is the session implementation meant to filter this attribute from the view of an application?
  3. DefaultNonceManager contains many persistent fields. Are all of these user specific? If not, why are they part of the persistent state?
  4. DefaultNonceManager creates a new thread in its constructor, and a new thread every time it is deserialized. Are we really allocating a thread per user?
  5. How is any of this meant to work in a distributed application?
    1. DefaultNonceManager uses a ScheduledExecutorService is to expire used nonces, i.e. to allow reuse after a given period of time. Are these scheduled tasks meant to be recreated on remote cluster members upon replication or redistribution? if so how?
    2. It seems to me that the map of used nonces within the DefaultNonceManager is critical to preventing a nonce from reuse. However, given there are many copies of this map, i.e. one referenced by the DefaultNonceManager of the DigestAuthenticationMechanism and a copy for each DefaultNonceManager per HttpSession, I do not understand how this can be enforced. Also, if the session storing the used nonces is invalidated, it seems to me that the associated nonces now be reusable - which smells like a security issue.
    3. As HttpSessions are redistributed in the cluster, what manages the lifecycle of these ScheduledExecutorService instances?
    4. WildFly allows web applications to use ProtoStream for marshalling session attributes - however, given that its class and constructors are package protected, and its state is completely opaque, WildFly cannot define a ProtoStream marshaller for it.

@pferraro
Copy link
Copy Markdown
Contributor

Looking at this again, it seems to me that this could benefit from a finer distinction between nonce state and nonce management. Would it not be better to persist nonce state to the session - rather than the mechanism by which nonces are managed?

@skyllarr
Copy link
Copy Markdown
Contributor Author

skyllarr commented Aug 19, 2025

A few questions about this proposal:

1. It was my understanding that a NonceManager is application-scoped, given that it is (or was, at least) associated with the DigestAuthenticationMechanism, which is associated with a deployment.  The changes here suggest that this is object is now scoped to a user/request, is that right?
   
   1. DefaultNonceManager contains NonceManager.setRequest(...)/getRequest(...) methods, suggesting it can only be associated with one request at a time?

@pferraro Yes the current request was associated with the NonceManager, it made some code changes simpler, but it was a bad choice. I removed this association and the NonceManager now does not hold the request.

   2. DefaultNonceManager is Serializable and stored in the HttpSession, meaning that new instances are recreated every time this HttpSession attribute is replicated or re-activated from passivated state.

yes

2. By storing the DefaultNonceManager in the HttpSession along with other user attributes - the DefaultNonceManager will accessible to applications via the HttpSession interface.  Is this intentional?  Or is the session implementation meant to filter this attribute from the view of an application?

This uses the HttpScope interface with the SESSION scope, which seemed the right place. I did not check how it is implemented though and if there is any filtering

3. DefaultNonceManager contains many persistent fields.  Are all of these user specific?  If not, why are they part of the persistent state?

I will go through the fields again and make sure the unneeded fields are transient/static, thank you

4. DefaultNonceManager creates a new thread in its constructor, and a new thread every time it is deserialized.  Are we really allocating a thread per user?

These constructors were present previously and I did not look into this TBH, but yes it doesn't seem the executor should need a thread per instance

5. How is any of this meant to work in a distributed application?
   
   1. DefaultNonceManager uses a ScheduledExecutorService is to expire used nonces, i.e. to allow reuse after a given period of time. Are these scheduled tasks meant to be recreated on remote cluster members upon replication or redistribution?  if so how?
   2. It seems to me that the map of used nonces within the DefaultNonceManager is critical to preventing a nonce from reuse.  However, given there are many copies of this map, i.e. one referenced by the DefaultNonceManager of the DigestAuthenticationMechanism and a copy for each DefaultNonceManager per HttpSession, I do not understand how this can be enforced.  Also, if the session storing the used nonces is invalidated, it seems to me that the associated nonces now be reusable - which smells like a security issue.
   3. As HttpSessions are redistributed in the cluster, what manages the lifecycle of these ScheduledExecutorService instances?
   4. WildFly allows web applications to use ProtoStream for marshalling session attributes - however, given that its class and constructors are package protected, and its state is completely opaque, WildFly cannot define a ProtoStream marshaller for it.

How I work with the used nonces map needs a revisit from me, there is a simple smoke test which is passing, but the problems you describe are there so the test is insufficient probably. If the used nonces should not be associated with the session, they need to be shared between nodes some other way

Thanks for feedback!

@skyllarr
Copy link
Copy Markdown
Contributor Author

Looking at this again, it seems to me that this could benefit from a finer distinction between nonce state and nonce management. Would it not be better to persist nonce state to the session - rather than the mechanism by which nonces are managed?

@pferraro Yes, true. The used nonces map shouldn't be persisted in session because of the points you raised. What would be a good way to share it between nodes in a cluster?

@pferraro
Copy link
Copy Markdown
Contributor

pferraro commented Aug 19, 2025

@pferraro Yes, true. The used nonces map shouldn't be persisted in session because of the points you raised. What would be a good way to share it between nodes in a cluster?

From what I can tell, this use case has the following consistency requirements:

  • Strict enforcement and tracking of nonce usage
    • e.g. if the same nonce were to be used to authenticate on multiple servers concurrently, only one should succeed.
  • Given that the age of a nonce is intrinsic, expiration of known nonces need not be strict

If my requirement assumptions are correct, I do not think that the HttpSession is suitable for tracking nonces, since it would otherwise allow the same nonce to be used by different sessions.
Additionally, we cannot guarantee that a given servlet container will support the requisite consistency guarantees for a distributed HttpSession as required by this use case. We would otherwise require that the container not allow concurrent access to a given session by multiple cluster members. While this is indeed a requirement of the servlet specification, there is no TCK test coverage that validates it, and so most containers do not enforce it. For that reason, the container ought to provides the mechanism by which nonce usage is tracked for distributed applications.

AFAICT, we should be able to model nonce tracking as a simple atomic counter. We can encapsulate this as a java.util.function.ToIntFunction<String>, where, for a given nonce, return 0 if not yet used, or an incremented value if used.
In the non-distributed case, this can be modeled by a simple ConcurrentMap<String, Integer> (or better yet, one provided by Caffeine, for which lifespan-based entry expiration can be configured):

e.g.

private final Map<String, Integer> map;

@Override
public int applyAsInt(String nonce) {
    return map.compute(nonce, (key, count) -> Optional.ofNullable(count).map(Math::incrementExact).orElse(0));
}

For the distributed case, we would model nonce tracking via a distributed atomic counter, where the container would provide the ToIntFunction implementation, e.g. one based on Infinispan using server managed configuration.
Thoughts?

@skyllarr
Copy link
Copy Markdown
Contributor Author

Thank you @pferraro , that sounds good to me. I need to take a look at the infinispan configurations and where this counter could be implemented to be able to update the proposal accordingly. I'll ping you once I update it, thanks for help!

@skyllarr
Copy link
Copy Markdown
Contributor Author

Closing as this RFE is postponed, backup branch is located at wildfly-security-incubator/wildfly-elytron

@skyllarr skyllarr closed this Feb 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants