Skip to content

Conversation

@Patrick-Pimentel-Bitwarden
Copy link
Contributor

@Patrick-Pimentel-Bitwarden Patrick-Pimentel-Bitwarden commented Nov 17, 2025

NEEDS TESTING ON FEATURE BRANCH

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-22975

📔 Objective

The objective of this ticket is to create a client version validator that checks the header data on a token request and prevent users who have migrated to v2 from logging in with older client versions.

  1. Extracted out KM utilities for checking isV2Encryption from RotateUserAccountKeysCommand
  2. Added some clarity and comments in BaseRequestValidator
  3. Installed the validation for client version in the appropriate place to prevent account enumeration

📸 Screenshots

Validator.Working.mov
Non.Rotated.Key.Working.Fine.mov

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

🦮 Reviewer guidelines

  • 👍 (:+1:) or similar for great changes
  • 📝 (:memo:) or ℹ️ (:information_source:) for notes or general info
  • ❓ (:question:) for questions
  • 🤔 (:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
  • 🎨 (:art:) for suggestions / improvements
  • ❌ (:x:) or ⚠️ (:warning:) for more significant problems or concerns needing attention
  • 🌱 (:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt
  • ⛏ (:pick:) for minor or nitpick changes

@github-actions
Copy link
Contributor

github-actions bot commented Nov 17, 2025

Logo
Checkmarx One – Scan Summary & Details40970053-bba7-43ab-83af-449c7a9a4f23

Great job! No new security vulnerabilities introduced in this pull request

Comment on lines +126 to +130
// 1.5 Now check the version number of the client. Do this after ValidateContextAsync so that
// we prevent account enumeration. If we were to do this before ValidateContextAsync, then attackers
// could use a known invalid client version and make a request for a user (before we know if they have
// demonstrated ownership of the account via correct credentials) and identify if they exist by getting
// an error response back from the validator saying the user is not compatible with the client.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

💭 Could someone double check my explanation here?

@claude
Copy link
Contributor

claude bot commented Nov 20, 2025

Claude finished @Patrick-Pimentel-Bitwarden's task —— View job


Code Review Complete

I've completed a comprehensive review of this PR. The implementation adds a client version validator to prevent v2 encryption users from logging in with older client versions. Overall, the approach is sound, but I've identified several findings that require attention.


Summary of Changes

What Changed:

  • Extracted v2 encryption detection utilities from RotateUserAccountKeysCommand into reusable queries
  • Added ClientVersionValidator to check client versions against a minimum requirement for v2 users
  • Integrated version validation into the authentication flow (after credential validation to prevent account enumeration)
  • Added comprehensive unit and integration tests

Status: The critical bug at BaseRequestValidator.cs:131 was already fixed in commit 91af02b9d, which is good. However, there are still several important findings below.


Critical Findings

Finding 1: Hardcoded version requires code changes for updates ⚠️

Location: src/Core/KeyManagement/Constants.cs:5

The minimum client version is hardcoded in the codebase. Future updates will require code changes, recompilation, and redeployment.

Recommendation:
Consider moving this to configuration (GlobalSettings) or a feature flag for easier updates without code changes. At minimum, add comprehensive documentation:

// Minimum client version required for v2 encryption users
// UPDATE THIS when v2 encryption support is added to new client releases
// Current value: 2025.11.0 - set for initial v2 encryption rollout (Nov 2025)
// History:
//   - 2025.11.0: Initial v2 encryption support
public static readonly Version MinimumClientVersion = new Version("2025.11.0");

Impact: Operational - requires code deployment to update version requirements


Finding 2: Invalid encryption state throws exception during login ⚠️

Location: src/Core/KeyManagement/Queries/IsV2EncryptionUserQuery.cs:27

When a user has mismatched encryption states (e.g., has signature key pair but v1 private key), the validation throws an InvalidOperationException, blocking login.

Current behavior:

_ => throw new InvalidOperationException(
    "User is in an invalid state for key rotation...")

Issue: The exception message doesn't include the user ID or specific state details, making support difficult. Consider:

  1. Improved logging: Log the user ID and state details before throwing
  2. More specific error: Include which combination was detected
_ => throw new InvalidOperationException(
    $"User {user.Id} is in an invalid encryption state: " +
    $"HasSignatureKeyPair={hasSignatureKeyPair}, " +
    $"IsPrivateKeyV2={isPrivateKeyEncryptionV2}. " +
    "Expected both true (v2) or both false (v1).")

Impact: Support burden - users in invalid state cannot login and error provides minimal debugging info


Finding 3: Error messages could be more specific for debugging 🎨

Location: src/Core/KeyManagement/Utilities/EncryptionParsing.cs:13, 24

The GetEncryptionType method throws "Invalid encryption type string" for two different conditions (wrong format and unrecognized type). More specific messages would aid debugging:

if (parts.Length == 1)
{
    throw new ArgumentException("Encryption type string must contain a type prefix and data separated by '.'");
}
// ... later ...
throw new ArgumentException($"Unrecognized encryption type: '{parts[0]}'");

Impact: Developer experience - clearer error messages speed up debugging


Finding 4: Null user handling may be fail-open 💭

Location: src/Identity/IdentityServer/RequestValidators/ClientVersionValidator.cs:21-26

When user is null, validation returns true (allowing authentication). Consider if this is the correct fail-open behavior:

public async Task<bool> ValidateAsync(User? user, CustomValidatorRequestContext requestContext)
{
    if (user == null)
    {
        return true;  // Fail-open: allows auth if user is null
    }

Question: Is this intentional for defense-in-depth, or should it fail-closed? If fail-open is correct, add a comment explaining why.

Recommendation: If this shouldn't happen in practice, consider logging a warning when this occurs for monitoring.


Code Quality Observations

Observation 1: Good security practice - validation order 👍

Location: src/Identity/IdentityServer/RequestValidators/BaseRequestValidator.cs:126-135

The comment at lines 126-130 correctly explains why client version validation occurs AFTER credential validation - to prevent account enumeration attacks. This is excellent security design and well-documented.


Observation 2: Test coverage is comprehensive 👍

The PR includes:

  • Unit tests for all new queries (IsV2EncryptionUserQuery, GetMinimumClientVersionForUserQuery, ClientVersionValidator)
  • Integration tests verifying end-to-end behavior
  • Tests for both feature flag states
  • Edge case testing (invalid states, null users, version comparisons)

Excellent test coverage overall.


Observation 3: Proper dependency injection 👍

Location: src/Identity/Utilities/ServiceCollectionExtensions.cs:28

The ClientVersionValidator is properly registered as transient in DI, and all validators correctly receive it through constructor injection.


PR Description Quality 💭

The PR description is good but could be improved:

Title: Clear and includes Jira reference ✓

Objective section: Explains what changed but could be clearer about WHY. Consider adding:

  • What problem does this solve?
  • What happens to v2 users on old clients without this change?
  • What is the user impact?

Screenshots: The videos are helpful for demonstrating behavior ✓

Missing:

  • Test plan: How was this tested beyond automated tests?
  • Rollout plan: How will this be deployed? Feature flag strategy?
  • Monitoring: How will we track blocked users? Are metrics/logs in place?
  • User communication: Have users been notified about minimum version requirements?

Security Review

Account enumeration prevention: Correctly validates credentials before client version to prevent username enumeration

Zero-knowledge principles: Does not log or expose sensitive encryption data

Fail-safe defaults: Uses proper null checks and validation

⚠️ Error message exposure: The error message "Please update your app to continue using Bitwarden" is generic and safe, but ensure no PII/sensitive data leaks in logs


Recommendations Summary

High Priority:

  1. Improve error message in IsV2EncryptionUserQuery to include user ID and state details for support
  2. Document (or reconfigure) the hardcoded version constant with update instructions
  3. Add monitoring/logging for users blocked by version requirements

Medium Priority:
4. Review and document null user handling in ClientVersionValidator
5. Make error messages in EncryptionParsing more specific
6. Consider configuration-based version management for easier updates

Low Priority:
7. Improve PR description with test plan, rollout strategy, and monitoring approach


Final Verdict

This is a well-implemented feature with good security considerations and comprehensive test coverage. The critical bug identified in the previous review was already fixed. The remaining findings are mostly improvements to error handling, logging, and operational concerns.

Status: ✅ Approved pending resolution of high-priority findings

Note: The author mentioned "100 failures in tests" - I'd recommend investigating those failures before merging. The likely cause was the missing return statement that was already fixed, but verification is needed.

@Patrick-Pimentel-Bitwarden Patrick-Pimentel-Bitwarden changed the title Auth/pm 22975/client version validator feat(auth-validator): [Auth/PM-22975] Client Version Validator Nov 20, 2025
public static EncryptionType GetEncryptionType(string encString)
{
var parts = encString.Split('.');
if (parts.Length == 1)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ The error message "Invalid encryption type string" is thrown for two different conditions (length check and parsing failure). Consider making these messages more specific for easier debugging:

if (parts.Length == 1)
{
    throw new ArgumentException("Encryption type string must contain a type prefix and data separated by '.'");
}

And for the second case:

throw new ArgumentException($"Unrecognized encryption type: '{parts[0]}'");

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is code copied from KM, I'm hesitant to make more changes to their work.

{
private static readonly string UpgradeMessage = "Please update your app to continue using Bitwarden";

public async Task<bool> ValidateAsync(User? user, CustomValidatorRequestContext requestContext)
Copy link
Contributor

Choose a reason for hiding this comment

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

💭 The null check for user returns true (allowing authentication to proceed). Consider whether this is the correct behavior:

  • Current behavior: If user is null (shouldn't happen in practice), validation passes
  • Alternative: Fail validation if user is null since we can't determine their encryption version

Which behavior is intentional here? If the current fail-open behavior is intentional for defense-in-depth, consider adding a comment explaining the reasoning.

true when isPrivateKeyEncryptionV2 => true,
// Valid v1 user
false when !isPrivateKeyEncryptionV2 => false,
_ => throw new InvalidOperationException(
Copy link
Contributor

Choose a reason for hiding this comment

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

Cryptographic state validation error lacks detail - The exception message doesn't specify which invalid state was detected, making debugging difficult. Consider:

_ => throw new InvalidOperationException(
    $"User {user.Id} is in an invalid encryption state: " +
    $"HasSignatureKeyPair={hasSignatureKeyPair}, " +
    $"IsPrivateKeyV2={isPrivateKeyEncryptionV2}. " +
    "Expected both true (v2) or both false (v1).")

This provides actionable information for support/debugging without exposing sensitive data.

return null;
}

if (await isV2EncryptionUserQuery.Run(user))
Copy link
Contributor

Choose a reason for hiding this comment

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

💭 Error handling consideration - If isV2EncryptionUserQuery.Run(user) throws an exception (e.g., invalid encryption state from line 27 of IsV2EncryptionUserQuery), should that:

  1. Propagate and fail the login attempt (current behavior)
  2. Be caught here and fail-open (return null allowing login)
  3. Be caught and fail-closed (return the minimum version, blocking login)

The current behavior (1) seems reasonable for detecting corruption, but consider whether it could be weaponized. If a user's account gets into an invalid state, they'll be locked out until support fixes it.

@Patrick-Pimentel-Bitwarden Patrick-Pimentel-Bitwarden marked this pull request as ready for review November 20, 2025 19:55
@Patrick-Pimentel-Bitwarden
Copy link
Contributor Author

I am seeing 100 failures in tests, double checking what is going on, consider holding off on approving.

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