-
Notifications
You must be signed in to change notification settings - Fork 1.5k
[PM-22696] send enumeration protection #6352
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
ike-kottlowski
merged 15 commits into
main
from
auth/pm-22696/send-enumeration-protection
Sep 23, 2025
Merged
Changes from 11 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
d4c3096
fix: rename flag for valid guid in request
ike-kottlowski 7b60abd
feat: add static enumeration helper
ike-kottlowski 505c62c
test: add enumeration helper tests
ike-kottlowski 7da8f04
feat: implement never authenticate validator
ike-kottlowski 3b161c4
chore: dotnet format
ike-kottlowski 1ec259b
chore: move test folders for better organization
ike-kottlowski 847bf14
test: using static class for common integration test setup
ike-kottlowski e2c2da5
fix: revert change in constants
ike-kottlowski fe95226
test: add integration tests
ike-kottlowski aba190e
test: update tests to use static helper
ike-kottlowski f028da9
test: unit test SendNeverAuthenticate method
ike-kottlowski d1bb7a9
fix: address feedback on PR
ike-kottlowski 368f218
Merge branch 'main' into auth/pm-22696/send-enumeration-protection
ike-kottlowski b114454
fix: rename static class clearly; rename enumeration protection method
ike-kottlowski f072576
fix: static class naming
ike-kottlowski File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
ike-kottlowski marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| ๏ปฟusing System.Text; | ||
|
|
||
| namespace Bit.Core.Utilities; | ||
|
|
||
| public static class EnumerationProtectionHelpers | ||
| { | ||
| /// <summary> | ||
| /// Use this method to get a consistent int result based on the salt that is in the range. | ||
| /// The same salt will always return the same index result based on range input. | ||
| /// </summary> | ||
| /// <param name="hmacKey">Key used to derive the HMAC hash. Use a different key for each usage for optimal security</param> | ||
| /// <param name="salt">The string to derive an index result</param> | ||
| /// <param name="range">The range of possible index values</param> | ||
| /// <returns>An int between 0 and range</returns> | ||
JaredSnider-Bitwarden marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| public static int GetIndexForSaltHash(byte[] hmacKey, string salt, int range) | ||
JaredSnider-Bitwarden marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| if (hmacKey == null || range <= 0 || hmacKey.Length == 0) | ||
| { | ||
| return 0; | ||
| } | ||
| else | ||
| { | ||
| // Compute the HMAC hash of the salt | ||
| var hmacMessage = Encoding.UTF8.GetBytes(salt.Trim().ToLowerInvariant()); | ||
| using var hmac = new System.Security.Cryptography.HMACSHA256(hmacKey); | ||
| var hmacHash = hmac.ComputeHash(hmacMessage); | ||
| // Convert the hash to a number | ||
| var hashHex = BitConverter.ToString(hmacHash).Replace("-", string.Empty).ToLowerInvariant(); | ||
| var hashFirst8Bytes = hashHex[..16]; | ||
| var hashNumber = long.Parse(hashFirst8Bytes, System.Globalization.NumberStyles.HexNumber); | ||
| // Find the default KDF value for this hash number | ||
| var hashIndex = (int)(Math.Abs(hashNumber) % range); | ||
| return hashIndex; | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
...tity/IdentityServer/RequestValidators/SendAccess/SendNeverAuthenticateRequestValidator.cs
JaredSnider-Bitwarden marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| ๏ปฟusing System.Text; | ||
| using Bit.Core.Settings; | ||
| using Bit.Core.Tools.Models.Data; | ||
| using Bit.Core.Utilities; | ||
| using Duende.IdentityServer.Models; | ||
| using Duende.IdentityServer.Validation; | ||
|
|
||
| namespace Bit.Identity.IdentityServer.RequestValidators.SendAccess; | ||
|
|
||
| public class SendNeverAuthenticateRequestValidator(GlobalSettings globalSettings) : ISendAuthenticationMethodValidator<NeverAuthenticate> | ||
| { | ||
| private readonly string[] _errorOptions = | ||
| [ | ||
| SendAccessConstants.EnumerationProtection.Guid, | ||
| SendAccessConstants.EnumerationProtection.Password, | ||
| SendAccessConstants.EnumerationProtection.Email | ||
| ]; | ||
|
|
||
| public Task<GrantValidationResult> ValidateRequestAsync( | ||
| ExtensionGrantValidationContext context, | ||
| NeverAuthenticate authMethod, | ||
| Guid sendId) | ||
| { | ||
| var neverAuthenticateError = GetErrorIndex(sendId, _errorOptions.Length); | ||
| var request = context.Request.Raw; | ||
| var errorType = neverAuthenticateError; | ||
|
|
||
| switch (neverAuthenticateError) | ||
| { | ||
| case SendAccessConstants.EnumerationProtection.Guid: | ||
| errorType = SendAccessConstants.GrantValidatorResults.InvalidSendId; | ||
| break; | ||
| case SendAccessConstants.EnumerationProtection.Email: | ||
| var hasEmail = request.Get(SendAccessConstants.TokenRequest.Email) is not null; | ||
| errorType = hasEmail ? SendAccessConstants.EmailOtpValidatorResults.EmailInvalid | ||
| : SendAccessConstants.EmailOtpValidatorResults.EmailRequired; | ||
| break; | ||
| case SendAccessConstants.EnumerationProtection.Password: | ||
| var hasPassword = request.Get(SendAccessConstants.TokenRequest.ClientB64HashedPassword) is not null; | ||
| errorType = hasPassword ? SendAccessConstants.PasswordValidatorResults.RequestPasswordDoesNotMatch | ||
| : SendAccessConstants.PasswordValidatorResults.RequestPasswordIsRequired; | ||
| break; | ||
| } | ||
|
|
||
| return Task.FromResult(BuildErrorResult(errorType)); | ||
| } | ||
|
|
||
| private static GrantValidationResult BuildErrorResult(string errorType) | ||
| { | ||
| // Create error response with custom response data | ||
| var customResponse = new Dictionary<string, object> | ||
| { | ||
| { SendAccessConstants.SendAccessError, errorType } | ||
| }; | ||
|
|
||
| var requestError = errorType switch | ||
| { | ||
| SendAccessConstants.EnumerationProtection.Guid => TokenRequestErrors.InvalidGrant, | ||
| SendAccessConstants.PasswordValidatorResults.RequestPasswordIsRequired => TokenRequestErrors.InvalidGrant, | ||
| SendAccessConstants.PasswordValidatorResults.RequestPasswordDoesNotMatch => TokenRequestErrors.InvalidRequest, | ||
| SendAccessConstants.EmailOtpValidatorResults.EmailInvalid => TokenRequestErrors.InvalidGrant, | ||
| SendAccessConstants.EmailOtpValidatorResults.EmailRequired => TokenRequestErrors.InvalidRequest, | ||
| _ => TokenRequestErrors.InvalidGrant | ||
| }; | ||
|
|
||
| return new GrantValidationResult(requestError, errorType, customResponse); | ||
| } | ||
|
|
||
| private string GetErrorIndex(Guid sendId, int range) | ||
| { | ||
| var salt = sendId.ToString(); | ||
| byte[] hmacKey = []; | ||
| if (CoreHelpers.SettingHasValue(globalSettings.SendDefaultHashKey)) | ||
| { | ||
| hmacKey = Encoding.UTF8.GetBytes(globalSettings.SendDefaultHashKey); | ||
| } | ||
|
|
||
| var index = EnumerationProtectionHelpers.GetIndexForSaltHash(hmacKey, salt, range); | ||
| return _errorOptions[index]; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.