Skip to content

feat(oauth2): Add allowed algorithms to profile - #1639

Merged
gtema merged 2 commits into
openstack-experimental:mainfrom
thinhphucnguyen:profile-allowed-algorithms
Nov 13, 2026
Merged

gtema merged 2 commits into
openstack-experimental:mainfrom
thinhphucnguyen:profile-allowed-algorithms

Conversation

@thinhphucnguyen

Copy link
Copy Markdown
Contributor

Summary

Step 1 of #1636. A subject-token validation profile now carries the JWT algorithms it accepts.

  • Native profiles keep exactly one algorithm, the configured signing algorithm.
  • External profiles default to RS256 and ES256. with_allowed_algorithms sets the list for an issuer.
  • The header alg is checked against the list before key lookup. An algorithm outside the list is rejected with AlgorithmNotAllowed. With a single-entry list the existing AlgorithmMismatch error is kept.
  • An empty list or one containing HS256/384/512 is an invalid profile.
  • verify_subject_token_jwt no longer takes expected_algorithm. The native constructors take the signing algorithm instead.

The identity provider has no algorithm list yet, so nothing sets a non-default list in this PR. That comes with the verifier switch in step 3.

Test plan

  • New tests: alg: none, HS256 and an out-of-list algorithm are rejected. PS256 and EdDSA are accepted when configured. An empty list or an HMAC entry is an invalid profile. The default list is RS256 and ES256.
  • Existing native tests are unchanged. One external-profile test was narrowed to RS256 because the old single-algorithm argument is gone.
  • cargo test -p openstack-keystone-core --lib oauth2_client
  • cargo clippy -p openstack-keystone-core --all-targets

Security review checklist

This changes token verification: which alg values a profile accepts. Native behaviour and errors are unchanged. Nothing reads scope, delegation, credentials or OPA input, so the checklist items are N/A.

Let a subject-token validation profile list the JWT algorithms it
accepts instead of pinning one through a separate argument of
verify_subject_token_jwt. Native profiles keep their single configured
algorithm; external profiles default to RS256 and ES256 and can be
widened per issuer with with_allowed_algorithms.

The header alg is checked against the list before any key lookup. An
empty list, or one that contains an HMAC algorithm, is rejected as an
invalid profile.

Part of openstack-experimental#1636.

SecurityImpact

Signed-off-by: Thinh Phuc Nguyen <nguyenthinhphuc1901@gmail.com>
Signed-off-by: Thinh Phuc Nguyen <nguyenthinhphuc1901@gmail.com>

@gtema gtema left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There are some nits with potential improvements/hardenings, but I am fine if you address them in the follow-up.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

fn is_symmetric(algorithm: Algorithm) -> bool {
matches!(
algorithm,
Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There is a theoretical gap here, if a new HS algorithm is added it would slip through. Instead a positive allowlist (RS*, PS*, ES*, EdDSA) would be safer

/// A profile with exactly one algorithm reports a mismatch with
/// [`TokenVerificationError::AlgorithmMismatch`], as the native profiles
/// always have.
fn resolve_profile_decoding_key(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

resolve_profile_decoding_key checks alg header against the profile's list, decoding_key_for finds JWK by kid and calls DecodingKey::from_jwk(jwk), verify_subject_token_jwt calls Validation::new(alg) with the header alg. The issue is that neither of those is comparing the JWK's own kty or alg with the header alg. A token can claim ES256, while it's kid points at an RSA key. The crates/jwt-crypto/src/backend.rs:new_verifier does the check, but we should have a test verifying this behavior (rejection on alg mismatch in this particular flow) to prevent regressions. So add the two tests to the PR or a follow-up: ES256 token against an RSA-only JWKS, and RS256 token against an EC-only JWKS. Both should return an error.

@gtema
gtema added this pull request to the merge queue Nov 13, 2026
Merged via the queue into openstack-experimental:main with commit f26e4da Nov 13, 2026
40 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants